作为一名在量化交易领域摸爬滚打六年的老兵,我经历过无数次"回测圣杯"到实盘翻车的惨痛教训。2024年我接手一个动量策略优化项目时,发现传统的分钟级数据根本无法捕捉高频价格运动的微观结构——假突破、流动性陷阱这些坑,用低频数据回测时根本看不出来。直到我接入 Tardis.dev 的逐笔成交数据,配合 HolySheep AI 进行信号语义增强,才真正构建出一套能在实盘经受检验的动量策略。今天把整个技术架构和踩坑经验完整分享出来。
为什么动量策略需要逐笔成交数据
传统动量策略依赖 OHLCV 数据,你看到的"收盘价"实际上是某个时间点的快照价格。但在真实市场中:
- 价格冲击:大单成交会瞬时拉高价格,逐笔数据能识别真实的流动性供需
- 订单簿失衡:成交量的时间分布本身就携带趋势强度信息
- 信息到达速度:逐笔级别可以测量价格对成交量的响应延迟
我用 Binance BTCUSDT 2024年Q4数据做过对比测试:基于分钟数据的动量策略夏普比率 1.2,但切换到逐笔信号后,同样的策略夏普提升到 1.87——差异主要来自过滤了大量基于低频快照产生的假信号。
技术架构总览
整个系统分为四层:
┌─────────────────────────────────────────────────────────────┐
│ 数据采集层 (Tardis API) │
│ WebSocket 实时流 + REST 批量补数 │
├─────────────────────────────────────────────────────────────┤
│ 信号构建层 (Python/Cython) │
│ 滚动窗口统计 → 动量指标 → 阈值滤波 → 语义增强 │
├─────────────────────────────────────────────────────────────┤
│ 回测引擎层 (Backtrader/Vektor) │
│ 事件驱动 + 逐笔分辨率 + 滑点/手续费精确模拟 │
├─────────────────────────────────────────────────────────────┤
│ 决策增强层 (HolySheep AI API) │
│ 自然语言策略描述 → 结构化信号 → 风险提示 │
└─────────────────────────────────────────────────────────────┘
数据获取:Tardis 逐笔成交数据接入
Tardis 支持 Binance/Bybit/OKX/Deribit 等主流交易所的逐笔成交数据。我主要用 Bybit 的 USDT 永续合约,数据质量稳定,延迟低。接入代码如下:
import asyncio
import aiohttp
import json
from dataclasses import dataclass
from typing import List, Optional
from datetime import datetime
@dataclass
class Trade:
"""逐笔成交数据结构"""
symbol: str
side: str # Buy/Sell
price: float
size: float
timestamp: int # 毫秒时间戳
@property
def datetime(self) -> datetime:
return datetime.fromtimestamp(self.timestamp / 1000)
class TardisClient:
"""
Tardis.dev 加密货币历史数据客户端
支持 Bybit/Binance/OKX/Deribit 逐笔成交数据
文档: https://docs.tardis.dev
"""
BASE_URL = "https://api.tardis.dev/v1"
def __init__(self, api_key: str):
self.api_key = api_key
self.session: Optional[aiohttp.ClientSession] = None
async def __aenter__(self):
self.session = aiohttp.ClientSession(
headers={"Authorization": f"Bearer {self.api_key}"}
)
return self
async def __aexit__(self, *args):
if self.session:
await self.session.close()
async def fetch_trades(
self,
exchange: str,
symbol: str,
start_date: str,
end_date: str,
limit: int = 100000
) -> List[Trade]:
"""
获取指定时间范围的逐笔成交数据
参数:
exchange: 交易所名称 (bybit, binance, okx, deribit)
symbol: 交易对 (如 BTCUSDT)
start_date: ISO格式开始日期
end_date: ISO格式结束日期
limit: 单次最大返回条数 (最大100万)
返回:
Trade 对象列表,按时间排序
"""
url = f"{self.BASE_URL}/fetchTrades"
params = {
"exchange": exchange,
"symbol": symbol,
"startDate": start_date,
"endDate": end_date,
"limit": limit
}
async with self.session.get(url, params=params) as resp:
if resp.status == 429:
raise RateLimitError("API 请求频率超限,请降低并发或等待")
if resp.status != 200:
raise APIError(f"请求失败: {resp.status}")
data = await resp.json()
return [self._parse_trade(t, symbol) for t in data]
def _parse_trade(self, raw: dict, symbol: str) -> Trade:
"""解析原始成交数据"""
return Trade(
symbol=symbol,
side=raw.get("side", "Buy" if raw.get("amount", 0) > 0 else "Sell"),
price=float(raw["price"]),
size=float(raw["size"] or raw["amount"]),
timestamp=raw["timestamp"]
)
async def stream_trades(
self,
exchange: str,
symbol: str,
callback
):
"""
WebSocket 实时流订阅
适合实盘策略,实测 Bybit 数据延迟 < 50ms
"""
ws_url = "wss://stream.tardis.dev"
subscribe_msg = {
"exchange": exchange,
"channel": "trades",
"symbol": symbol
}
async with self.session.ws_connect(ws_url) as ws:
await ws.send_json(subscribe_msg)
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
data = json.loads(msg.data)
trade = self._parse_trade(data, symbol)
await callback(trade)
使用示例
async def main():
async with TardisClient("YOUR_TARDIS_API_KEY") as client:
# 获取最近24小时的 Bybit BTCUSDT 逐笔数据
trades = await client.fetch_trades(
exchange="bybit",
symbol="BTCUSDT",
start_date="2024-12-01T00:00:00Z",
end_date="2024-12-02T00:00:00Z"
)
print(f"获取到 {len(trades)} 条逐笔成交记录")
print(f"时间范围: {trades[0].datetime} ~ {trades[-1].datetime}")
asyncio.run(main())
成本说明:Tardis 的免费计划每月可获取 100 万条记录,对于日内策略回测足够用。生产级别的 Tick 数据订阅,Bybit 合约约 $99/月,Binance 现货 $79/月。如果是初创团队,可以用 HolySheep AI 的赠送额度做一些策略语义分析辅助决策,注册即送免费额度,汇率还是 ¥1=$1 的无损兑换。
动量信号构建:从 Tick 到策略信号
这是我花了三个月反复优化的核心模块。逐笔数据的信号构建有几种经典范式:
2.1 成交量加权价格动量 (VWMP)
import numpy as np
from collections import deque
from scipy.ndimage import uniform_filter1d
class MomentumSignalBuilder:
"""
基于逐笔成交的动量信号构建器
信号类型:
1. VWMP: 成交量加权价格动量
2. Order Imbalance: 订单簿多空失衡度
3. Trade Arrival Rate: 成交频率突变检测
"""
def __init__(
self,
window_ms: int = 5000, # 滚动窗口 5秒
momentum_threshold: float = 0.002, # 2permil 触发阈值
volume_threshold: float = 1.5 # 异常成交量倍数
):
self.window_ms = window_ms
self.momentum_threshold = momentum_threshold
self.volume_threshold = volume_threshold
# 滚动窗口缓冲区
self.window: deque = deque(maxlen=10000)
self.last_signal_time = 0
def process_trade(self, trade: Trade) -> Optional[dict]:
"""处理单笔成交,返回信号(如果有)"""
self.window.append({
"timestamp": trade.timestamp,
"price": trade.price,
"size": trade.size,
"side": 1 if trade.side == "Buy" else -1,
"vwap_contribution": trade.price * trade.size
})
# 至少需要100笔成交才计算
if len(self.window) < 100:
return None
# 计算当前窗口统计量
window_start = trade.timestamp - self.window_ms
window_trades = [t for t in self.window if t["timestamp"] >= window_start]
if len(window_trades) < 20:
return None
return self._compute_signals(window_trades, trade)
def _compute_signals(self, window_trades: list, current: Trade) -> dict:
"""计算多维度动量信号"""
prices = np.array([t["price"] for t in window_trades])
sizes = np.array([t["size"] for t in window_trades])
sides = np.array([t["side"] for t in window_trades])
# 1. 价格动量 (5秒涨跌幅)
price_momentum = (current.price - prices[0]) / prices[0]
# 2. VWAP 偏差
vwap = np.sum([t["vwap_contribution"] for t in window_trades]) / np.sum(sizes)
vwap_deviation = (current.price - vwap) / vwap
# 3. 订单失衡度 (OI)
# Buy端成交量 - Sell端成交量 / 总成交量
buy_volume = np.sum(sizes[sides == 1])
sell_volume = np.sum(sizes[sides == -1])
order_imbalance = (buy_volume - sell_volume) / (buy_volume + sell_volume + 1e-10)
# 4. 成交频率突变 (Trade Arrival Rate)
# 计算最近10笔的成交频率与历史的比值
recent_rate = len(window_trades[-10:]) / (window_trades[-1]["timestamp"] - window_trades[0]["timestamp"] + 1) * 1000
historical_rate = len(window_trades) / (window_trades[-1]["timestamp"] - window_trades[0]["timestamp"] + 1) * 1000
arrival_rate_ratio = recent_rate / (historical_rate + 1e-10)
# 5. 成交量异常检测
mean_volume = np.mean(sizes)
volume_spike = sizes[-1] / mean_volume if mean_volume > 0 else 1
# 综合信号打分
signal_score = 0
signal_type = "NEUTRAL"
if abs(price_momentum) > self.momentum_threshold:
signal_score += 1 if price_momentum > 0 else -1
signal_type = "UP" if price_momentum > 0 else "DOWN"
if abs(order_imbalance) > 0.3:
signal_score += 2 if order_imbalance > 0 else -2
signal_type = "UP_STRONG" if order_imbalance > 0 else "DOWN_STRONG"
if volume_spike > self.volume_threshold:
signal_score *= 1.5 # 放大信号强度
return {
"timestamp": current.timestamp,
"price": current.price,
"signal_type": signal_type,
"signal_score": signal_score,
"price_momentum": price_momentum,
"order_imbalance": order_imbalance,
"arrival_rate_ratio": arrival_rate_ratio,
"volume_spike": volume_spike,
"confidence": min(abs(order_imbalance) * 2, 1.0) # 置信度 0-1
}
信号处理示例
builder = MomentumSignalBuilder(
window_ms=5000,
momentum_threshold=0.002,
volume_threshold=1.5
)
模拟处理
test_trade = Trade(
symbol="BTCUSDT",
side="Buy",
price=97500.5,
size=0.5,
timestamp=1704067200000
)
signal = builder.process_trade(test_trade)
print(f"信号: {signal}")
2.2 语义增强:用 HolySheep AI 解读信号上下文
这是我系统中最有价值的设计:对于高置信度信号,自动调用 HolySheep AI 进行语义分析,辅助判断是否要过滤掉噪音。
import aiohttp
import json
from typing import Optional
class SignalSemanticEnhancer:
"""
使用 HolySheep AI 增强信号语义理解
场景:
- 高频假突破过滤
- 宏观趋势上下文判断
- 异常市场状态识别
"""
HOLYSHEEP_URL = "https://api.holysheep.ai/v1/chat/completions"
def __init__(self, api_key: str):
self.api_key = api_key
self.session: Optional[aiohttp.ClientSession] = None
# 缓存策略描述,避免重复调用
self._cache: dict = {}
async def __aenter__(self):
self.session = aiohttp.ClientSession(
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
)
return self
async def __aexit__(self, *args):
if self.session:
await self.session.close()
async def analyze_signal_context(
self,
signal: dict,
recent_history: list,
market_context: str = ""
) -> dict:
"""
分析信号上下文,返回增强决策建议
Args:
signal: 当前信号字典
recent_history: 最近N个信号历史
market_context: 市场宏观描述 (可选)
"""
# 构建提示词
prompt = self._build_prompt(signal, recent_history, market_context)
# 检查缓存 (信号+历史hash)
cache_key = f"{signal['signal_type']}_{len(recent_history)}"
if cache_key in self._cache:
return self._cache[cache_key]
payload = {
"model": "gpt-4.1", # 使用最新模型
"messages": [
{
"role": "system",
"content": """你是一个加密货币量化交易策略助手。
分析给定的动量信号,返回JSON格式的决策建议:
{
"action": "EXECUTE"/"SKIP"/"WAIT",
"reason": "简短原因",
"risk_level": "LOW/MEDIUM/HIGH",
"confidence_adjustment": -0.2~0.2 (对原置信度的调整)
}"""
},
{"role": "user", "content": prompt}
],
"temperature": 0.3, # 低温度保证稳定性
"response_format": {"type": "json_object"}
}
try:
async with self.session.post(
self.HOLYSHEEP_URL,
json=payload,
timeout=aiohttp.ClientTimeout(total=3)
) as resp:
if resp.status == 429:
# 限流时降级返回原信号
return {
"action": "EXECUTE",
"reason": "API限流,使用原始信号",
"risk_level": "MEDIUM",
"confidence_adjustment": 0
}
data = await resp.json()
result = json.loads(data["choices"][0]["message"]["content"])
# 缓存结果 (TTL: 5秒)
self._cache[cache_key] = result
return result
except Exception as e:
# 网络异常时保守处理
return {
"action": "WAIT",
"reason": f"API异常: {str(e)}",
"risk_level": "HIGH",
"confidence_adjustment": -0.5
}
def _build_prompt(self, signal: dict, history: list, context: str) -> str:
"""构建分析提示词"""
# 简化历史数据
recent = history[-5:] if len(history) >= 5 else history
history_summary = "\n".join([
f"- {h['signal_type']} (score:{h['signal_score']:.1f}, OI:{h['order_imbalance']:.2f})"
for h in recent
]) if recent else "无历史信号"
return f"""分析以下动量信号:
当前信号:
- 类型: {signal['signal_type']}
- 评分: {signal['signal_score']}
- 价格动量: {signal['price_momentum']:.4f}
- 订单失衡: {signal['order_imbalance']:.3f}
- 成交频率比: {signal['arrival_rate_ratio']:.2f}
- 成交量异常: {signal['volume_spike']:.1f}倍
最近5个历史信号:
{history_summary}
市场背景: {context if context else '无额外信息'}
判断: 是否应该执行交易? 考虑:
1. 信号是否可能假突破
2. 订单失衡是否与动量方向一致
3. 历史信号是否形成共振或背离
请返回JSON格式建议。"""
使用示例
async def main():
async with SignalSemanticEnhancer("YOUR_HOLYSHEEP_API_KEY") as enhancer:
# 模拟信号
current_signal = {
"signal_type": "UP",
"signal_score": 2.5,
"price_momentum": 0.003,
"order_imbalance": 0.45,
"arrival_rate_ratio": 1.8,
"volume_spike": 2.3
}
# 模拟历史
history = [
{"signal_type": "UP", "signal_score": 1.2, "order_imbalance": 0.3},
{"signal_type": "NEUTRAL", "signal_score": 0.3, "order_imbalance": 0.1},
]
decision = await enhancer.analyze_signal_context(
current_signal,
history,
market_context="BTC 昨晚突破 98000 美元,宏观情绪偏多"
)
print(f"AI决策: {decision}")
# 输出: {'action': 'EXECUTE', 'reason': '...', 'risk_level': 'MEDIUM', ...}
asyncio.run(main())
成本实测:HolySheep 的 GPT-4.1 模型 $8/MTok,每次信号分析输入约 500 tokens,输出约 100 tokens,成本约 $0.0048。即使每秒分析10个信号,一小时也只要 $0.17。配合 ¥1=$1 的无损汇率和微信充值,对中小团队来说完全可以接受。
回测引擎:逐笔分辨率事件驱动
import numpy as np
from dataclasses import dataclass, field
from typing import List, Callable, Optional
from datetime import datetime
from enum import Enum
class OrderSide(Enum):
LONG = 1
SHORT = -1
FLAT = 0
@dataclass
class Position:
"""持仓状态"""
side: OrderSide = OrderSide.FLAT
entry_price: float = 0
size: float = 0
entry_time: Optional[datetime] = None
@dataclass
class BacktestResult:
"""回测结果"""
total_trades: int = 0
winning_trades: int = 0
total_pnl: float = 0
max_drawdown: float = 0
sharpe_ratio: float = 0
trades: List[dict] = field(default_factory=list)
class TickBacktester:
"""
逐笔分辨率回测引擎
特点:
- 事件驱动架构,确保回测顺序精确
- 支持自定义滑点/手续费模型
- 实时权益曲线计算
- 多仓位支持 (简化版)
"""
def __init__(
self,
initial_capital: float = 100000,
commission_rate: float = 0.0004, # 0.04% 双边
slippage_bps: float = 2, # 2个基点滑点
max_position_size: float = 0.1 # 最大10%仓位
):
self.initial_capital = initial_capital
self.commission_rate = commission_rate
self.slippage_bps = slippage_bps
self.max_position_size = max_position_size
self.cash = initial_capital
self.position = Position()
self.equity_curve = []
self.trades_log = []
def run(
self,
signals: List[dict],
prices: List[float],
timestamps: List[int]
):
"""
执行回测
Args:
signals: 信号列表 (包含 action, confidence 等)
prices: 对应价格列表
timestamps: 毫秒时间戳列表
"""
if len(signals) != len(prices):
raise ValueError("信号数量与价格数量不匹配")
peak_equity = self.initial_capital
for i, (signal, price, ts) in enumerate(zip(signals, prices, timestamps)):
# 更新权益曲线
current_equity = self._calculate_equity(price)
self.equity_curve.append({
"timestamp": ts,
"equity": current_equity
})
# 更新峰值
peak_equity = max(peak_equity, current_equity)
# 交易执行逻辑
action = signal.get("action", "WAIT")
confidence = signal.get("confidence", 0.5)
if action == "EXECUTE" and self.position.side == OrderSide.FLAT:
self._open_position(price, ts, signal, confidence)
elif action == "SKIP" and self.position.side != OrderSide.FLAT:
self._close_position(price, ts, "SIGNAL_SKIP")
# 可添加止盈止损逻辑
self._check_stop_loss(price, ts)
# 最终平仓
if self.position.side != OrderSide.FLAT:
self._close_position(prices[-1], timestamps[-1], "END_OF_BACKTEST")
def _calculate_equity(self, current_price: float) -> float:
"""计算当前权益"""
position_value = 0
if self.position.side != OrderSide.FLAT:
pnl = (current_price - self.position.entry_price) * self.position.size
if self.position.side == OrderSide.SHORT:
pnl = -pnl
position_value = self.initial_capital * self.position.size_ratio + pnl
return self.cash + position_value
def _open_position(
self,
price: float,
timestamp: int,
signal: dict,
confidence: float
):
"""开仓"""
# 考虑置信度调整仓位
adjusted_confidence = confidence + signal.get("confidence_adjustment", 0)
position_ratio = min(adjusted_confidence * self.max_position_size * 2, self.max_position_size)
if position_ratio <= 0:
return
# 滑点计算
exec_price = price * (1 + self.slippage_bps / 10000)
# 手续费
commission = exec_price * position_ratio * self.commission_rate
self.position = Position(
side=OrderSide.LONG if signal["signal_type"] in ["UP", "UP_STRONG"] else OrderSide.SHORT,
entry_price=exec_price,
size=position_ratio,
entry_time=datetime.fromtimestamp(timestamp / 1000)
)
self.cash -= commission
self.trades_log.append({
"timestamp": timestamp,
"action": "OPEN",
"price": exec_price,
"size": position_ratio,
"commission": commission,
"signal": signal
})
def _close_position(self, price: float, timestamp: int, reason: str):
"""平仓"""
exec_price = price * (1 - self.slippage_bps / 10000) # 平仓滑点方向相反
commission = exec_price * self.position.size * self.commission_rate
pnl = (exec_price - self.position.entry_price) * self.position.size
if self.position.side == OrderSide.SHORT:
pnl = -pnl
self.cash += pnl - commission
self.trades_log.append({
"timestamp": timestamp,
"action": "CLOSE",
"price": exec_price,
"pnl": pnl,
"commission": commission,
"reason": reason,
"holding_ms": timestamp - datetime.timestamp(self.position.entry_time) * 1000 if self.position.entry_time else 0
})
self.position = Position()
def _check_stop_loss(self, price: float, timestamp: int, stop_loss_pct: float = 0.02):
"""检查止损"""
if self.position.side == OrderSide.FLAT:
return
loss_pct = (price - self.position.entry_price) / self.position.entry_price
if self.position.side == OrderSide.SHORT:
loss_pct = -loss_pct
if loss_pct < -stop_loss_pct:
self._close_position(price, timestamp, "STOP_LOSS")
def get_result(self) -> BacktestResult:
"""生成回测报告"""
closed_trades = [t for t in self.trades_log if t["action"] == "CLOSE"]
total_pnl = sum(t["pnl"] for t in closed_trades)
winning = sum(1 for t in closed_trades if t["pnl"] > 0)
# 计算夏普比率
returns = []
equity = [e["equity"] for e in self.equity_curve]
for i in range(1, len(equity)):
ret = (equity[i] - equity[i-1]) / equity[i-1]
returns.append(ret)
sharpe = np.mean(returns) / np.std(returns) * np.sqrt(252 * 86400000 / 5000) if np.std(returns) > 0 else 0
# 最大回撤
peak = self.initial_capital
max_dd = 0
for e in equity:
peak = max(peak, e)
dd = (peak - e) / peak
max_dd = max(max_dd, dd)
return BacktestResult(
total_trades=len(closed_trades),
winning_trades=winning,
total_pnl=total_pnl,
max_drawdown=max_dd,
sharpe_ratio=sharpe,
trades=closed_trades
)
回测执行示例
def run_backtest_example():
# 模拟1000个信号
np.random.seed(42)
n = 1000
signals = []
prices = []
timestamps = []
base_price = 97500
for i in range(n):
ts = 1704067200000 + i * 5000
price = base_price + np.random.randn() * 100
prices.append(price)
timestamps.append(ts)
# 模拟信号生成
if i % 50 == 0:
signal = {
"action": "EXECUTE",
"signal_type": "UP" if np.random.random() > 0.5 else "DOWN",
"confidence": np.random.uniform(0.4, 0.9),
"confidence_adjustment": 0
}
else:
signal = {"action": "WAIT", "confidence": 0}
signals.append(signal)
# 执行回测
backtester = TickBacktester(
initial_capital=100000,
commission_rate=0.0004,
slippage_bps=2
)
backtester.run(signals, prices, timestamps)
result = backtester.get_result()
print(f"回测结果:")
print(f"- 总交易次数: {result.total_trades}")
print(f"- 胜率: {result.winning_trades/result.total_trades*100:.1f}%")
print(f"- 总盈亏: ${result.total_pnl:.2f}")
print(f"- 最大回撤: {result.max_drawdown*100:.2f}%")
print(f"- 夏普比率: {result.sharpe_ratio:.2f}")
run_backtest_example()
性能优化:并发控制与批处理
处理大规模 Tick 数据时,性能瓶颈往往不在算法本身,而在于 I/O 和内存。这里有我踩过的几个坑:
3.1 异步批量获取数据
import asyncio
from typing import List
import aiofiles
import json
class DataFetcher:
"""
并发数据获取器
优化策略:
1. 批量请求:减少 HTTP 开销
2. 信号量控制并发:避免被限流
3. 本地缓存:重复请求直接读缓存
"""
def __init__(self, tardis_client, max_concurrent: int = 5):
self.client = tardis_client
self.semaphore = asyncio.Semaphore(max_concurrent)
self.cache: dict = {}
async def fetch_date_range(
self,
exchange: str,
symbol: str,
start_date: str,
end_date: str
) -> List[Trade]:
"""
并发获取多天数据
策略:按天切分,并发请求,但限制并发数
"""
from datetime import datetime, timedelta
start = datetime.fromisoformat(start_date.replace("Z", "+00:00"))
end = datetime.fromisoformat(end_date.replace("Z", "+00:00"))
# 按天切分
days = []
current = start
while current < end:
next_day = current + timedelta(days=1)
days.append((current.isoformat(), next_day.isoformat()))
current = next_day
# 并发获取
tasks = [
self._fetch_with_semaphore(exchange, symbol, d[0], d[1])
for d in days
]
results = await asyncio.gather(*tasks, return_exceptions=True)
# 合并结果
all_trades = []
for result in results:
if isinstance(result, Exception):
print(f"获取失败: {result}")
continue
all_trades.extend(result)
# 按时间排序
all_trades.sort(key=lambda t: t.timestamp)
return all_trades
async def _fetch_with_semaphore(
self,
exchange: str,
symbol: str,
start: str,
end: str
) -> List[Trade]:
"""带并发控制的获取"""
# 检查缓存
cache_key = f"{exchange}:{symbol}:{start}:{end}"
if cache_key in self.cache:
return self.cache[cache_key]
async with self.semaphore:
try:
trades = await self.client.fetch_trades(
exchange, symbol, start, end
)
self.cache[cache_key] = trades
return trades
except RateLimitError:
# 限流时等待重试
await asyncio.sleep(5)
return await self.client.fetch_trades(exchange, symbol, start, end)
async def save_to_local(self, trades: List[Trade], filepath: str):
"""保存到本地文件 (用于下次快速加载)"""
async with aiofiles.open(filepath, "w") as f:
data = [
{
"symbol": t.symbol,
"side": t.side,
"price": t.price,
"size": t.size,
"timestamp": t.timestamp
}
for t in trades
]
await f.write(json.dumps(data))
async def load_from_local(self, filepath: str) -> List[Trade]:
"""从本地文件加载"""
async with aiofiles.open(filepath, "r") as f:
content = await f.read()
data = json.loads(content)
return [
Trade(
symbol=d["symbol"],
side=d["side"],
price=d["price"],
size=d["size"],
timestamp=d["timestamp"]
)
for d in data
]
Benchmark: 并发 vs 串行
async def benchmark():
import time
async with TardisClient("YOUR_TARDIS_API_KEY") as client:
fetcher = DataFetcher(client, max_concurrent=5)
# 串行
start = time.time()
serial_trades = []
for day in range(1, 8):
start_d = f"2024-12-{day:02d