feat(crypto-tail): 策略最小价差(无/固定/自动) + 前端默认与文案
- 后端: 最小价差 DB/Entity/DTO、Binance K线 REST+WS、自动价差 IQR 预计算与执行时校验 - 前端: 最小价差(自动-固定-无),默认自动,label 旁 info 说明,选择自动不展示建议约 - i18n: minSpreadModeTip 说明不写死标的 - 文档: crypto-tail-strategy-min-spread-flow.md - scripts: Binance K线拉取与 WS 示例 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
从币安现货 API 获取 BTC/USDC 15 分钟 K 线数据。
|
||||
|
||||
API: https://api.binance.com/api/v3/klines
|
||||
无需 API Key,公开行情接口。
|
||||
|
||||
使用方法:
|
||||
python3 scripts/fetch_binance_btc_usdc_klines.py
|
||||
python3 scripts/fetch_binance_btc_usdc_klines.py --limit 96
|
||||
python3 scripts/fetch_binance_btc_usdc_klines.py --limit 10 --interval 15m
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
BINANCE_BASE = "https://api.binance.com"
|
||||
|
||||
|
||||
def fetch_klines(
|
||||
symbol: str = "BTCUSDC",
|
||||
interval: str = "15m",
|
||||
limit: int = 500,
|
||||
start_time: int | None = None,
|
||||
end_time: int | None = None,
|
||||
) -> list[list] | None:
|
||||
"""
|
||||
获取 K 线数据。
|
||||
返回每根 K 线: [ openTime, open, high, low, close, volume, closeTime, ... ]
|
||||
"""
|
||||
params = {"symbol": symbol, "interval": interval, "limit": limit}
|
||||
if start_time is not None:
|
||||
params["startTime"] = start_time
|
||||
if end_time is not None:
|
||||
params["endTime"] = end_time
|
||||
qs = urllib.parse.urlencode(params)
|
||||
url = f"{BINANCE_BASE}/api/v3/klines?{qs}"
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "PolymarketBot/1.0 (script)"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
return json.load(resp)
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode() if e.fp else ""
|
||||
try:
|
||||
err = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
err = {"msg": body}
|
||||
print(f"Request failed: {e.code} - {err}", file=sys.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Request error: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Fetch Binance BTC/USDC 15m klines")
|
||||
parser.add_argument("--symbol", default="BTCUSDC", help="Trading pair (default: BTCUSDC)")
|
||||
parser.add_argument("--interval", default="15m", help="Kline interval (default: 15m)")
|
||||
parser.add_argument("--limit", type=int, default=20, help="Number of klines (default: 20, max 1000)")
|
||||
parser.add_argument("--start", type=int, default=None, help="Start time (ms)")
|
||||
parser.add_argument("--end", type=int, default=None, help="End time (ms)")
|
||||
args = parser.parse_args()
|
||||
|
||||
limit = max(1, min(1000, args.limit))
|
||||
|
||||
print("=== Binance BTC/USDC K-line (15m) ===\n")
|
||||
print(f"Symbol: {args.symbol} Interval: {args.interval} Limit: {limit}")
|
||||
if args.start:
|
||||
print(f"Start: {args.start} ({time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(args.start // 1000))})")
|
||||
if args.end:
|
||||
print(f"End: {args.end} ({time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(args.end // 1000))})")
|
||||
print()
|
||||
|
||||
klines = fetch_klines(
|
||||
symbol=args.symbol,
|
||||
interval=args.interval,
|
||||
limit=limit,
|
||||
start_time=args.start,
|
||||
end_time=args.end,
|
||||
)
|
||||
|
||||
if not klines:
|
||||
print("No kline data returned")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Got {len(klines)} kline(s)\n")
|
||||
print("Columns: openTime, open, high, low, close, volume, closeTime, ...")
|
||||
print("-" * 72)
|
||||
for k in klines:
|
||||
open_ts_ms = k[0]
|
||||
open_ts = open_ts_ms // 1000
|
||||
ts_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(open_ts))
|
||||
o, h, l, c, v = k[1], k[2], k[3], k[4], k[5]
|
||||
print(f" {ts_str} O:{o} H:{h} L:{l} C:{c} V:{v}")
|
||||
print("-" * 72)
|
||||
last = klines[-1]
|
||||
print(f"Latest: open={last[1]}, high={last[2]}, low={last[3]}, close={last[4]}, volume={last[5]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user