CRT Analyzer Pro: Binance USDT Backtest & 25-Coin Filter
🧩 Syntax:
# -*- 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())