"""下载样本外验证用的新币种数据,存成与 freqtrade 一致的 feather。 现有结论全部建立在 BTC/ETH/SOL 三个币上,参数(级别对、tol、SL/TP)也是 在这三个币上挑的,存在选择偏差。本脚本补齐一批完全没参与过调参的品种。 只拉 30m/2h:那是实测最强的一对,用它做样本外足够,且请求量只有全级别的四成。 限速要点(踩过的坑): klines(limit=1500) 权重 30,上限 2400/分钟 = 80 次/分钟 = 0.75s/次, 0.8s 间隔正好卡在边缘,一旦触发 429,短退避跨不过计数窗口就会连续失败。 故间隔放到 1.3s、权重阈值压到 1400、429 时等满一个窗口。 另外分页失败不再丢弃整只币,已抓到的部分照样落盘。 """ from __future__ import annotations import sys import time from pathlib import Path import pandas as pd import requests OUT = Path(__file__).resolve().parents[1] / "data" / "binance" / "futures" BASE = "https://fapi.binance.com/fapi/v1/klines" PROXY = {"http": "http://127.0.0.1:7897", "https": "http://127.0.0.1:7897"} SYMBOLS = ["BNB", "XRP", "DOGE", "ADA", "AVAX", "LINK", "LTC", "TRX"] TFS = ["2h", "30m"] START_MS = int(pd.Timestamp("2019-01-01", tz="UTC").timestamp() * 1000) COLS = ["date", "open", "high", "low", "close", "volume"] GAP = 1.3 WEIGHT_CAP = 1400 def fetch(sess: requests.Session, symbol: str, tf: str) -> pd.DataFrame | None: rows: list[list] = [] cur = START_MS while True: batch = None for attempt in range(6): try: r = sess.get(BASE, params={"symbol": f"{symbol}USDT", "interval": tf, "startTime": cur, "limit": 1500}, timeout=40) if r.status_code in (418, 429): time.sleep(65) continue if r.status_code == 400: return None r.raise_for_status() batch = r.json() if int(r.headers.get("X-MBX-USED-WEIGHT-1M", 0) or 0) > WEIGHT_CAP: time.sleep(40) break except Exception: time.sleep(5 * (attempt + 1)) if not batch: break # 抓不动或抓完了,保留已有部分 rows.extend(batch) nxt = int(batch[-1][0]) + 1 if nxt <= cur or len(batch) < 1500: break cur = nxt time.sleep(GAP) if len(rows) < 1000: return None df = pd.DataFrame(rows).iloc[:, :6] df.columns = ["ts", "open", "high", "low", "close", "volume"] df["date"] = pd.to_datetime(pd.to_numeric(df["ts"]), unit="ms", utc=True) for c in ("open", "high", "low", "close", "volume"): df[c] = pd.to_numeric(df[c]) return df[COLS].drop_duplicates("date").sort_values("date").reset_index(drop=True) def main() -> None: OUT.mkdir(parents=True, exist_ok=True) sess = requests.Session() sess.proxies.update(PROXY) jobs = [(s, tf) for tf in TFS for s in SYMBOLS] print(f"[下载] {len(jobs)} 个任务,单线程 {GAP}s 间隔", flush=True) for i, (sym, tf) in enumerate(jobs, 1): path = OUT / f"{sym}_USDT_USDT-{tf}-futures.feather" if path.exists(): print(f" [{i}/{len(jobs)}] {sym} {tf} 已存在", flush=True) continue df = fetch(sess, sym, tf) if df is None: print(f" [{i}/{len(jobs)}] {sym} {tf} 失败", flush=True) continue df.to_feather(path) print(f" [{i}/{len(jobs)}] {sym} {tf} {len(df)} 根 " f"{df['date'].min():%Y-%m-%d}~{df['date'].max():%Y-%m-%d}", flush=True) if __name__ == "__main__": sys.exit(main())