← Назад
""" Step 1: Download kline data and save to disk. Usage: python3 backtests/download_data.py --days 7 --top 35 Output: backtests/data_cache_7d.pkl """ import sys, os, time, pickle, argparse import numpy as np from datetime import datetime sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from pybit.unified_trading import HTTP VWAP_PERIOD = 50 NATR_PERIOD = 14 CHOP_PERIOD = 14 BLACKLIST = {"BTCUSDT", "ETHUSDT", "USDCUSDT", "BTCPERP", "ETHPERP"} def fetch_klines(session, symbol, interval="5", days=7): all_klines = [] bars_needed = days * 24 * 60 // int(interval) end_time = int(datetime.now().timestamp() * 1000) while len(all_klines) < bars_needed: try: resp = session.get_kline( category="linear", symbol=symbol, interval=interval, limit=1000, end=end_time, ) if resp["retCode"] != 0: break items = resp["result"]["list"] if not items: break for item in items: all_klines.append({ "ts": int(item[0]), "h": float(item[2]), "l": float(item[3]), "c": float(item[4]), "v": float(item[5]), }) end_time = int(items[-1][0]) - 1 if len(items) < 1000: break except Exception as e: print(f" ERR {symbol}: {e}"); break time.sleep(0.15) all_klines.reverse() seen = set(); unique = [] for k in all_klines: if k["ts"] not in seen: seen.add(k["ts"]); unique.append(k) return unique[-bars_needed:] if len(unique) > bars_needed else unique def get_top_symbols(session, top_n=35, min_vol=20_000_000): tickers = session.get_tickers(category="linear") cands = [] for t in tickers["result"]["list"]: sym = t["symbol"] if not sym.endswith("USDT") or sym in BLACKLIST: continue vol = float(t.get("turnover24h", 0)) if vol >= min_vol: cands.append((sym, vol)) cands.sort(key=lambda x: x[1], reverse=True) return cands[:top_n] def calc_zvwap(h, l, c, v, period=VWAP_PERIOD): n = len(c); z = np.full(n, 0.0) for i in range(period, n): hi=h[i-period:i]; lo=l[i-period:i]; ci=c[i-period:i]; vi=v[i-period:i] tp=(hi+lo+ci)/3; ctv=np.cumsum(tp*vi); cv=np.cumsum(vi) cvs=np.where(cv==0,1,cv); va=ctv/cvs; vwap=va[-1] std=np.std(ci-va) if std>0: z[i]=(c[i]-vwap)/std return z def calc_natr(h, l, c, period=NATR_PERIOD): n=len(c); natr=np.full(n,0.0); tr=np.zeros(n) for i in range(1,n): tr[i]=max(h[i]-l[i],abs(h[i]-c[i-1]),abs(l[i]-c[i-1])) for i in range(period,n): atr=np.mean(tr[i-period+1:i+1]) natr[i]=(atr/c[i])*100 if c[i]>0 else 0 return natr def calc_chop(h, l, c, period=CHOP_PERIOD): n=len(c); chop=np.full(n,50.0); tr=np.zeros(n) for i in range(1,n): tr[i]=max(h[i]-l[i],abs(h[i]-c[i-1]),abs(l[i]-c[i-1])) for i in range(period,n): atr_sum=np.sum(tr[i-period+1:i+1]) highest=np.max(h[i-period+1:i+1]); lowest=np.min(l[i-period+1:i+1]) rng=highest-lowest if rng>0: chop[i]=100*np.log10(atr_sum/rng)/np.log10(period) return chop def main(): parser = argparse.ArgumentParser() parser.add_argument("--days", type=int, default=7) parser.add_argument("--top", type=int, default=35) args = parser.parse_args() session = HTTP(testnet=False) print(f"Fetching top {args.top} symbols...") symbols = get_top_symbols(session, args.top) print(f" Found {len(symbols)} symbols") print(f"Downloading {args.days}d klines...") data_cache = {} for idx, (sym, vol) in enumerate(symbols): klines = fetch_klines(session, sym, days=args.days) if len(klines) < VWAP_PERIOD + 50: print(f" SKIP {sym} ({len(klines)} bars)") continue h=np.array([k["h"] for k in klines]) l=np.array([k["l"] for k in klines]) c=np.array([k["c"] for k in klines]) v=np.array([k["v"] for k in klines]) data_cache[sym] = { "h": h, "l": l, "c": c, "v": v, "z": calc_zvwap(h,l,c,v), "natr": calc_natr(h,l,c), "chop": calc_chop(h,l,c), "vol24h": vol, } print(f" [{idx+1}/{len(symbols)}] {sym} — {len(klines)} bars, vol ${vol/1e6:.0f}M") time.sleep(0.2) out_path = os.path.join(os.path.dirname(__file__), f"data_cache_{args.days}d.pkl") with open(out_path, "wb") as f: pickle.dump({"symbols": data_cache, "days": args.days, "date": datetime.now().isoformat()}, f) print(f"\n✅ Saved {len(data_cache)} symbols to {out_path}") print(f" File size: {os.path.getsize(out_path)/1024:.0f} KB") if __name__ == "__main__": main()