# -*- coding: utf-8 -*- """ CRT Analyzer v3 PRO - Tek Dosya (Aktif=25, Süresiz Blacklist, Açılışta Otomatik Eleme) Gerekenler: Python 3.10+, pip install pandas numpy ccxt psutil requests Timeframe'ler: 15m, 1h, 4h Bar sayısı: DEFAULT_BARS (varsayılan 1000; sistem kaldırırsa 5000 yapabilirsin) Yapılanlar: - Binance USDT spot evrenini indir (hacme göre sırala). - Süresiz blacklist'i oku; aday havuzdan çıkar. - 25 coin hedefi için sırayla hafif backtest yap (batch ve beklemeli). - Winrate ve AvgPnL eşiğini geçenler active_list'e eklenir, geçmeyenler blacklist'e yazılır. - 25 tamamlanana kadar dış aday çekmeye devam eder. - Telegram'a özet mesajlar gönderir (başladı / eklenenler / blackliste düşenler / dinlenmeye geçti vs.) Dikkat: - TOKEN ve CHAT_ID değerlerini aşağıda doldur. """ import os, json, time, math, asyncio, statistics from datetime import datetime, timezone, timedelta import numpy as np import pandas as pd import ccxt.async_support as ccxt import requests import psutil # ===================== K U R U L U M / A Y A R L A R ===================== # >>>>> BURAYA KENDİ TOKEN VE CHAT_ID'NI YAZ <<<<< MAIN_BOT_TOKEN = os.environ.get("TG_MAIN_BOT_TOKEN", "PASTE_YOURS") # örn: 8207581334:AAF5IO-... RESULT_BOT_TOKEN = os.environ.get("TG_RESULT_BOT_TOKEN", "PASTE_YOURS") # ikinci bot istersen MAIN_CHAT_ID = int(os.environ.get("TG_MAIN_CHAT_ID", "1541732373")) # senin chat id # Hedef aktif sayısı TARGET_ACTIVE = 25 # Timeframe'ler ve bar TIMEFRAMES = ["15m", "1h", "4h"] DEFAULT_BARS = 1000 # sistemin güçlüyse 5000 yap # Eşikler WINRATE_TH = 60.0 # % AVG_PNL_TH = 0.5 # % (işlem başına) MIN_TRADES_TH = 10 # Kaynak koruma CPU_MAX = 85 # % üstü ise dinlen RAM_MIN_FREE_GB = 2.0 # 2 GB'den az boş RAM kalırsa dinlen REST_SECONDS = 30 # dinlenme süresi # Batch ayarları BATCH_SIZE = 6 # aynı anda kaç coin test edilecek API_DELAY_RANGE = (0.15, 0.6) # veri çekiş araları # Klasörler BASE_DIR = os.path.join(os.getcwd(), "crt_data") CACHE_DIR = os.path.join(BASE_DIR, "cache") BK_DIR = os.path.join(BASE_DIR, "backtest") os.makedirs(BASE_DIR, exist_ok=True) os.makedirs(CACHE_DIR, exist_ok=True) os.makedirs(BK_DIR, exist_ok=True) # Dosyalar ACTIVE_FILE = os.path.join(BASE_DIR, "active_list.json") BLACKLIST_FILE = os.path.join(BASE_DIR, "blacklist.json") UNIVERSE_FILE = os.path.join(BASE_DIR, "coin_universe.json") SCAN_FILE = os.path.join(BASE_DIR, "scan_results.json") ERROR_LOG = os.path.join(BASE_DIR, "error.log") # ===================== Y A R D I M C I F O N K S I Y O N L A R ===================== def nowu(): return datetime.now(timezone.utc) def tzs(dt): return dt.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z") def logerr(e: Exception): try: with open(ERROR_LOG, "a", encoding="utf-8") as f: f.write(f"{nowu().isoformat()} :: {repr(e)}\n") except: pass def send_tg(token, chat_id, text): try: url = f"https://api.telegram.org/bot{token}/sendMessage" requests.post(url, json={"chat_id": chat_id, "text": text, "parse_mode": "Markdown"}) except Exception as e: logerr(e) def loadj(path, default): try: if os.path.exists(path): with open(path, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: logerr(e) return default def savej(path, data): try: with open(path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) except Exception as e: logerr(e) def wait_system_sync(): """CPU/RAM yüksekse bloklayarak dinlenir.""" while True: try: cpu = psutil.cpu_percent(interval=1.0) avail_gb = psutil.virtual_memory().available / (1024**3) if cpu < CPU_MAX and avail_gb > RAM_MIN_FREE_GB: return # dinlenme send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, f"⏸️ Dinlenme: CPU={cpu:.0f}% | Boş RAM={avail_gb:.1f} GB. {REST_SECONDS}s bekleniyor…") time.sleep(REST_SECONDS) except Exception as e: logerr(e) time.sleep(REST_SECONDS) return def rand_delay(): a, b = API_DELAY_RANGE time.sleep(np.random.uniform(a, b)) # ===================== G Ö S T E R G E L E R (Pandas/Numpy) ===================== def rsi(series: pd.Series, period=14): # Wilder RSI delta = series.diff() up = np.where(delta > 0, delta, 0.0) down = np.where(delta < 0, -delta, 0.0) roll_up = pd.Series(up, index=series.index).ewm(alpha=1/period, adjust=False).mean() roll_down = pd.Series(down, index=series.index).ewm(alpha=1/period, adjust=False).mean() rs = roll_up / (roll_down.replace(0, np.nan)) rsi = 100 - (100 / (1 + rs)) return rsi.fillna(50.0) def atr(high: pd.Series, low: pd.Series, close: pd.Series, period=14): prev_close = close.shift(1) tr = pd.concat([ (high - low).abs(), (high - prev_close).abs(), (low - prev_close).abs() ], axis=1).max(axis=1) atr = tr.ewm(alpha=1/period, adjust=False).mean() return atr def ema(series: pd.Series, period=200): return series.ewm(span=period, adjust=False).mean() def adx(high, low, close, period=14): # +DM, -DM up_move = high.diff() down_move = low.diff().mul(-1) plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0.0) minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0.0) tr = pd.concat([ (high - low).abs(), (high - close.shift(1)).abs(), (low - close.shift(1)).abs() ], axis=1).max(axis=1) atr14 = tr.ewm(alpha=1/period, adjust=False).mean() plus_di = 100 * (pd.Series(plus_dm, index=high.index).ewm(alpha=1/period, adjust=False).mean() / atr14.replace(0,np.nan)) minus_di = 100 * (pd.Series(minus_dm, index=high.index).ewm(alpha=1/period, adjust=False).mean() / atr14.replace(0,np.nan)) dx = ( (plus_di - minus_di).abs() / (plus_di + minus_di).replace(0, np.nan) ) * 100 adx_val = dx.ewm(alpha=1/period, adjust=False).mean() return adx_val.fillna(20.0) # ===================== V E R I C E K M E & O H L C V ===================== def tf_to_ms(tf: str) -> int: if tf.endswith("m"): return int(tf[:-1]) * 60 * 1000 if tf.endswith("h"): return int(tf[:-1]) * 60 * 60 * 1000 if tf.endswith("d"): return int(tf[:-1]) * 24 * 60 * 60 * 1000 raise ValueError("unsupported tf") async def fetch_ohlcv_full(ex, symbol: str, timeframe: str, bars: int) -> pd.DataFrame: """ Binance limitleri nedeniyle art arda çekiyoruz. ccxt limit genelde 1500; burada 1000'lik parçalarla gidiyoruz. """ limit_per_call = 1000 need = bars since = None out = [] while need > 0: try: if since is None: # en sonlardan başlayıp geriye doğru değil, ileri doğru almak için since'i geçmişe çekebiliriz; # ancak ccxt fetch_ohlcv genelde since'ten ileri alır. Burada since None ise son barları alır. o = await ex.fetch_ohlcv(symbol, timeframe=timeframe, limit=min(limit_per_call, need)) else: o = await ex.fetch_ohlcv(symbol, timeframe=timeframe, since=since, limit=min(limit_per_call, need)) if not o: break out.extend(o) # ileri kaydır since = o[-1][0] + 1 need -= len(o) rand_delay() except Exception as e: logerr(e) rand_delay() break if not out: return pd.DataFrame(columns=["time","open","high","low","close","volume"]) df = pd.DataFrame(out, columns=["time","open","high","low","close","volume"]) df["time"] = pd.to_datetime(df["time"], unit="ms", utc=True) for c in ["open","high","low","close","volume"]: df[c] = pd.to_numeric(df[c], errors="coerce") df = df.dropna().drop_duplicates(subset=["time"]).set_index("time").sort_index() return df # ===================== B A S I T S T R A T E J I & B A C K T E S T ===================== def simple_signals_and_pnl(df: pd.DataFrame, tf: str): """ Basit ve sağlam bir kriter: - Trend: close vs EMA200 (aynı TF) - Momentum: ADX(14) > 20 - Giriş bandı: BUY -> RSI 50..68, SELL -> RSI 32..50 - TP/SL: ATR(14) x 1.0 / 1.0 - LOOKAHEAD: 3 bar boyunca TP/SL'ye hangisi önce değerse o sayılır. Dönen: trades, wins, avg_pnl_pct """ if len(df) < 250: return 0, 0, 0.0 df = df.copy() df["rsi"] = rsi(df["close"], 14) df["atr"] = atr(df["high"], df["low"], df["close"], 14) df["ema200"] = ema(df["close"], 200) df["adx"] = adx(df["high"], df["low"], df["close"], 14) trades = 0 wins = 0 pnls = [] LOOK = 3 ATR_MULT = 1.0 closes = df["close"].values highs = df["high"].values lows = df["low"].values rsis = df["rsi"].values atrs = df["atr"].values ema200s= df["ema200"].values adxs = df["adx"].values idxs = np.arange(len(df)) for i in range(220, len(df) - LOOK - 1): c = closes[i]; a = atrs[i]; r = rsis[i]; e = ema200s[i]; ad = adxs[i] if np.isnan([c,a,r,e,ad]).any(): continue # BUY sinyali if c > e and ad > 20 and 50 <= r <= 68: entry = c; tp = entry + ATR_MULT * a; sl = entry - ATR_MULT * a hit = None for j in range(1, LOOK+1): hi = highs[i+j]; lo = lows[i+j] if lo <= sl: hit = "SL"; break if hi >= tp: hit = "TP"; break if hit: trades += 1 if hit == "TP": wins += 1 pnl = (tp/entry - 1.0)*100.0 else: pnl = (sl/entry - 1.0)*100.0 pnls.append(pnl) # SELL sinyali elif c < e and ad > 20 and 32 <= r <= 50: entry = c; tp = entry - ATR_MULT * a; sl = entry + ATR_MULT * a hit = None for j in range(1, LOOK+1): hi = highs[i+j]; lo = lows[i+j] if hi >= sl: hit = "SL"; break if lo <= tp: hit = "TP"; break if hit: trades += 1 if hit == "TP": wins += 1 pnl = (1.0 - tp/entry)*100.0 else: pnl = (1.0 - sl/entry)*100.0 pnls.append(pnl) if trades == 0: return 0, 0, 0.0 avg_pnl = float(np.nanmean(pnls)) if pnls else 0.0 return trades, wins, avg_pnl async def backtest_coin(ex, symbol: str, bars: int): """ Coin'i verilen TF'lerde test eder. Sonuç: total_trades, winrate, avg_pnl """ total_trades = 0 total_wins = 0 pnl_list = [] for tf in TIMEFRAMES: df = await fetch_ohlcv_full(ex, symbol, tf, bars) if df.empty: continue t, w, a = simple_signals_and_pnl(df, tf) total_trades += t total_wins += w if not math.isnan(a): pnl_list.append(a) if total_trades == 0: return {"symbol": symbol, "trades": 0, "winrate": 0.0, "avg_pnl": 0.0} winrate = (total_wins / total_trades) * 100.0 avg_pnl = float(np.nanmean(pnl_list)) if pnl_list else 0.0 return {"symbol": symbol, "trades": total_trades, "winrate": winrate, "avg_pnl": avg_pnl} # ===================== U N I V E R S E & L I S T E L E R ===================== def norm_symbol_binance(sym: str) -> str: if sym.endswith("USDT"): return f"{sym[:-4]}/USDT" return sym def fetch_binance_universe_sync(max_symbols=300): """ Sync isteklerle Binance evreni (USDT spot) ve 24h hacme göre sıralama """ try: exinfo = requests.get("https://api.binance.com/api/v3/exchangeInfo", timeout=10).json() symbols_meta = exinfo.get("symbols", []) usdt = [] for s in symbols_meta: if s.get("status") != "TRADING": continue if s.get("quoteAsset") != "USDT": continue # spot izin kontrolü (esnek tutuyoruz) perms = s.get("permissions") or [] if perms and ("SPOT" not in perms): continue usdt.append(s.get("symbol")) if not usdt: return [] t24 = requests.get("https://api.binance.com/api/v3/ticker/24hr", timeout=10).json() vol_map = {} for it in t24: ss = it.get("symbol") if ss in usdt: try: qv = float(it.get("quoteVolume", 0.0)) except: qv = 0.0 vol_map[ss] = qv ranked = sorted(usdt, key=lambda x: vol_map.get(x, 0.0), reverse=True) ranked = ranked[:max_symbols] normed = [norm_symbol_binance(s) for s in ranked] return normed except Exception as e: logerr(e) return [] def load_active(): return set(loadj(ACTIVE_FILE, [])) def save_active(active_set): savej(ACTIVE_FILE, sorted(list(active_set))) def load_blacklist(): return set(loadj(BLACKLIST_FILE, [])) def save_blacklist(black): savej(BLACKLIST_FILE, sorted(list(black))) def append_scan_result(rec): data = loadj(SCAN_FILE, []) data.append(rec) savej(SCAN_FILE, data) # ===================== A N A I S L E M A K I S I ===================== async def try_fill_active_list(): """ - Universe'i indir veya dosyadan yükle. - Blacklist'i uygula. - Active < TARGET_ACTIVE ise sırayla adayları test et ve doldur. """ send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, f"🚀 Başlatıldı: {tzs(nowu())}\nHedef aktif: {TARGET_ACTIVE}, TF: {', '.join(TIMEFRAMES)}, Bars: {DEFAULT_BARS}") # Universe universe = loadj(UNIVERSE_FILE, []) if not universe: universe = fetch_binance_universe_sync(300) if universe: savej(UNIVERSE_FILE, universe) if not universe: send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, "⚠️ Universe alınamadı, çıkıyorum.") return # Listeler active = load_active() black = load_blacklist() # Blacklist'i evrenden çıkar, ayrıca aktifte yanlışlıkla varsa temizle universe = [u for u in universe if u not in black] active = {a for a in active if a not in black} # Zaten 25 ve üstüyse sadece bilgi ver if len(active) >= TARGET_ACTIVE: save_active(active) send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, f"ℹ️ Zaten {len(active)} aktif var. İşlem yok.") return # Aday kuyruğu (aktif ve blacklist dışında kalanlar) queue = [u for u in universe if u not in active and u not in black] if not queue: send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, "⚠️ Aday kalmadı (blacklist dışı). Universe güncellemesi gerekebilir.") return # CCXT exchange ex = ccxt.binance({"enableRateLimit": True, "timeout": 10000}) added = [] blacked = [] try: # Batch halinde dene idx = 0 while len(active) < TARGET_ACTIVE and idx < len(queue): wait_system_sync() current_batch = queue[idx: idx+BATCH_SIZE] idx += BATCH_SIZE tasks = [backtest_coin(ex, sym, DEFAULT_BARS) for sym in current_batch] results = await asyncio.gather(*tasks, return_exceptions=True) for res in results: if isinstance(res, Exception): logerr(res) continue # kayıt append_scan_result({ "time": nowu().isoformat(), "symbol": res["symbol"], "trades": res["trades"], "winrate": round(res["winrate"],2), "avg_pnl": round(res["avg_pnl"],3) }) sym = res["symbol"] ok = (res["trades"] >= MIN_TRADES_TH and res["winrate"] >= WINRATE_TH and res["avg_pnl"] >= AVG_PNL_TH) if ok: if sym not in active: active.add(sym) added.append(sym) else: black.add(sym) blacked.append(sym) save_active(active) save_blacklist(black) if added: send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, f"✅ Aktife eklenenler ({len(added)}): {', '.join(added)}") added = [] if blacked: send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, f"⛔ Blacklist ({len(blacked)}): {', '.join(blacked)}") blacked = [] # 25 olduysa çık if len(active) >= TARGET_ACTIVE: break # Son durum save_active(active) save_blacklist(black) send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, f"📌 Tamamlandı.\nAktif: {len(active)}/{TARGET_ACTIVE}\nBlacklist: {len(black)}\nSaat: {tzs(nowu())}") # Özet tablo (son 25-50 kayıt) last = loadj(SCAN_FILE, []) if last: tail = last[-min(50, len(last)):] good = [x for x in tail if x["symbol"] in active] bad = [x for x in tail if x["symbol"] in black] send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, f"ℹ️ Son tarama özeti: ✓{len(good)} uygun, ✗{len(bad)} uygun değil.") except Exception as e: logerr(e) send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, f"❌ Hata: {repr(e)}") finally: try: await ex.close() except: pass # ===================== Ç A L I Ş T I R M A N O K T A S I ===================== if __name__ == "__main__": # İlk kurulum için dosyaları oluştur if not os.path.exists(ACTIVE_FILE): save_active(set()) if not os.path.exists(BLACKLIST_FILE): save_blacklist(set()) if not os.path.exists(SCAN_FILE): savej(SCAN_FILE, []) if not os.path.exists(UNIVERSE_FILE): savej(UNIVERSE_FILE, []) # Telegram'a başlamayı bildir try: send_tg(MAIN_BOT_TOKEN, MAIN_CHAT_ID, "🟢 CRT Analyzer v3 PRO (Eleme Modu) başlıyor…") except Exception as e: logerr(e) # Çalıştır asyncio.run(try_fill_active_list())