最近其实还在恶补理论基础,但是大家好像对理论没太大兴趣😪,都没什么人看。理论学久了确实也容易累,今天下午就基于 backtrader 回测了一个经典策略——双均线策略,分享给大家~
提前声明:这个策略我没跑过实盘,仅供技术交流,盈亏自负!!
核心思路
之前介绍移动平均线(MA)的时候有简单提过,MA 最常见的做法就是找“黄金交叉”,不了解的小伙伴可以翻前面的文章。
策略主要使用使用短期均线和长期均线:
当短期均线上穿长期均线 → 买入(做多)
当短期均线下穿长期均线 → 卖出(平仓)
依赖库:
backtrader:量化策略回测框架,之前介绍过
binance.client:连接币安 API 获取历史数据
pandas:结构化数据处理
matplotlib:绘图模块
datetime:处理起止时间
实现步骤:
1. 抓取 BN 中的 BTC/USDT 数据
# 初始化币安客户端
client = Client()
# 获取历史 k 线数据
def get_binance_btc_data(symbol='BTCUSDT', interval='1d', lookback_days=600):
end_time = datetime.datetime.now()
start_time = end_time - datetime.timedelta(days=lookback_days)
klines = client.get_historical_klines(
symbol,
interval,
start_str=start_time.strftime("%d %b %Y %H:%M:%S"),
end_str=end_time.strftime("%d %b %Y %H:%M:%S")
)
df = pd.DataFrame(klines, columns=[
'timestamp', 'open', 'high', 'low', 'close', 'volume',
'close_time', 'quote_asset_volume', 'number_of_trades',
'taker_buy_base_asset_volume', 'taker_buy_quote_asset_volume', 'ignore'
])
df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('datetime', inplace=True)
df = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
return df
df = get_binance_btc_data()
2. 输出处理成 backtrader 兼容
class PandasData(bt.feeds.PandasData):
params = (
('datetime', None),
('open', 'open'),
('high', 'high'),
('low', 'low'),
('close', 'close'),
('volume', 'volume'),
('openinterest', -1),
)
3. 定义双均线策略
class MovingAverageCrossStrategy(bt.Strategy):
params = (
('short_period', 10),
('long_period', 30),
)
def __init__(self):
self.sma_short = bt.indicators.SimpleMovingAverage(self.data.close, period=self.params.short_period)
self.sma_long = bt.indicators.SimpleMovingAverage(self.data.close, period=self.params.long_period)
self.crossover = bt.indicators.CrossOver(self.sma_short, self.sma_long)
self.order = None # 当前挂单
self.buy_price = None
def next(self):
if self.order:
return # 如果有挂单在等待,就不下新单
if not self.position:
if self.crossover > 0:
self.order = self.buy()
elif self.crossover < 0:
self.order = self.sell()
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return # 订单提交中,忽略
if order.status in [order.Completed]:
if order.isbuy():
self.buy_price = order.executed.price
print(f'🟢 买入: {order.executed.price:.2f} @ {bt.num2date(order.executed.dt)}')
elif order.issell():
pnl = order.executed.price - self.buy_price
pnl_pct = (pnl / self.buy_price) * 100
print(f'🔴 卖出: {order.executed.price:.2f} @ {bt.num2date(order.executed.dt)}')
print(f'💰 本次盈亏: {pnl:.2f} USDT ({pnl_pct:.2f}%)')
self.order = None # 重置订单引用
def notify_trade(self, trade):
if trade.isclosed:
print(f'✅ 交易完成: 毛利: {trade.pnl:.2f} USDT, 净利: {trade.pnlcomm:.2f} USDT')
4. 设置 backtrader 的 cerebro
if __name__ == '__main__':
cerebro = bt.Cerebro()
# 设置初始资金
cerebro.broker.setcash(10000.0)
# 设置手续费
cerebro.broker.setcommission(commission=0.0008)
# 调用 k 线数据
data = PandasData(dataname=df)
cerebro.adddata(data)
# 添加策略
cerebro.addstrategy(MovingAverageCrossStrategy)
# 结果分析,配置 analyzers
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
# 运行回测
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
results = cerebro.run()
strat = results[0]
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
# 输出分析结果
sharpe = strat.analyzers.sharpe.get_analysis()
drawdown = strat.analyzers.drawdown.get_analysis()
returns = strat.analyzers.returns.get_analysis()
print(f"🔹 Sharpe Ratio: {sharpe.get('sharperatio', 'N/A')}")
print(f"🔹 Max Drawdown: {drawdown['max']['drawdown']:.2f}%")
print(f"🔹 Total Return: {returns['rtot']*100:.2f}%")
print(f"🔹 Annual Return: {returns['rnorm']*100:.2f}%")
print(f"🔹 Average Return: {returns['ravg']*100:.2f}%")
5. 绘图
cerebro.plot(
style='candlestick',
barup='green',
bardown='red',
grid=True,
volume=True,
figsize=(18, 9),
dpi=120
系统记录了每笔交易的具体信息,以及最终的分析结果,可以看到在常规参数设置下(短期 10 天,长期 30 天),能到达到年化 12.95%。
6. 初步优化
首先想到的就是通过调整参数来进行优化,找到更合适的参数设置。初步我设置了 3 个参数,时间间隔,短期和长期。
intervals = ['1d', '12h']
short_range = range(5, 21, 3)
long_range = range(20, 61, 5)
遍历区间内的所有组合后得到回报率最高的组合,并且绘制出图形:
可以看到在最佳参数组合下回报率可以达到 53.42%,年化 25.15%。
优化
这就是今天下午做的工作啦,但是大佬说这个有很大问题,都没有跑赢 beta。也还有很多可以优化的地方:
1. 支持多币种,分散风险
2. 细化参数设置
...
代码仓库:
https://github.com/Ashley0324/crypto_quant
走过路过的老板们给个✨吧!新建了一个交流群,链接也在仓库里,欢迎一起交流学习~
也别忘了关注公众号!努力产出干货,天天向上~
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。