策略复现:BTC 双均线策略,最高年化 25.15%(附代码)
2025-04-09 21:23
泡芙的元宇宙
2025-04-09 21:23
订阅此专栏
收藏此文章

最近其实还在恶补理论基础,但是大家好像对理论没太大兴趣😪,都没什么人看。理论学久了确实也容易累,今天下午就基于 backtrader 回测了一个经典策略——双均线策略,分享给大家~


提前声明:这个策略我没跑过实盘,仅供技术交流,盈亏自负!!


核心思路

之前介绍移动平均线(MA)的时候有简单提过,MA 最常见的做法就是找“黄金交叉”,不了解的小伙伴可以翻前面的文章。


量化学习笔记(九):量化指标——移动平均线和 MACD


策略主要使用使用短期均线和长期均线:


当短期均线上穿长期均线 → 买入(做多)


当短期均线下穿长期均线 → 卖出(平仓)


依赖库:

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 dfdf = 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=(189),    dpi=120


系统记录了每笔交易的具体信息,以及最终的分析结果,可以看到在常规参数设置下(短期 10 天,长期 30 天),能到达到年化 12.95%。


6. 初步优化

首先想到的就是通过调整参数来进行优化,找到更合适的参数设置。初步我设置了 3 个参数,时间间隔,短期和长期。

intervals = ['1d''12h']    short_range = range(5213)    long_range = range(20615)

遍历区间内的所有组合后得到回报率最高的组合,并且绘制出图形:



可以看到在最佳参数组合下回报率可以达到 53.42%,年化 25.15%。


优化

这就是今天下午做的工作啦,但是大佬说这个有很大问题,都没有跑赢 beta。也还有很多可以优化的地方:

1. 支持多币种,分散风险

2. 细化参数设置

...


代码仓库:

https://github.com/Ashley0324/crypto_quant

走过路过的老板们给个✨吧!新建了一个交流群,链接也在仓库里,欢迎一起交流学习~


也别忘了关注公众号!努力产出干货,天天向上~

【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。

泡芙的元宇宙
数据请求中
查看更多

推荐专栏

数据请求中
在 App 打开