You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Backtrader回测自定义AI反转策略?无买卖信号问题排查

问题诊断与修复方案

核心问题分析

你的策略未生成买卖信号和组合价值变化,主要源于以下关键错误:

1. 实时时间判断与回测逻辑冲突

回测模式下,datetime.now() 获取的是当前系统时间,而非数据中的历史时间,导致:

  • 周末判断逻辑错误,跳过大量历史数据
  • 分钟间隔判断完全不匹配历史数据时间戳,模型训练流程从未启动,self.model 始终为 None,AI 预测逻辑无法触发

2. 缺失数据获取方法

策略中调用 self.get_data() 但未定义该方法,无法获取训练数据,模型训练流程中断

3. 数据处理语法错误

prepare_data 和 prepare_features 方法中,data.datetime.get(size(len(self.macd))) 应为 data.datetime.get(size=len(self.macd)),直接抛出语法错误中断执行

4. 目标变量前瞻偏差

TargetIndicator 中用当前收盘价对比前一根收盘价(self.data.close[0] > self.data.close[-1]),回测中 self.data.close[-1] 属于未来数据,导致前瞻偏差,且与模型训练时的目标变量定义不一致


修复后的完整代码

import backtrader as bt
import pandas as pd
import numpy as np
import warnings
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import accuracy_score

warnings.filterwarnings("ignore", category=FutureWarning, module="ta.trend")

# 自定义绩效分析器:追踪组合价值与盈亏
class PerformanceAnalyzer(bt.Analyzer):
    def __init__(self):
        self.portfolio_values = []
        self.pnl = []

    def notify_trade(self, trade):
        if trade.isclosed:
            self.pnl.append(trade.pnl)

    def next(self):
        self.portfolio_values.append(self.strategy.broker.get_value())

    def get_analysis(self):
        return {
            'cumulative_pnl': sum(self.pnl),
            'average_pnl': np.mean(self.pnl) if self.pnl else 0,
            'max_pnl': max(self.pnl) if self.pnl else 0,
            'min_pnl': min(self.pnl) if self.pnl else 0,
            'portfolio_values': self.portfolio_values
        }

# 自定义目标指标:预测下一根K线涨跌(无前瞻偏差)
class TargetIndicator(bt.Indicator):
    lines = ('target',)

    def next(self):
        # 用前两根K线对比,避免使用未来数据
        if len(self.data) >= 2:
            self.lines.target[0] = 1 if self.data.close[-1] > self.data.close[-2] else 0
        else:
            self.lines.target[0] = 0

# AI反转策略
class AIReversalStrategy(bt.Strategy):
    params = (
        ('symbol', 'BTCUSD'),
        ('interval', 15),  # 匹配24小时15分钟K线的策略逻辑
        ('train_window', 96),  # 24小时对应的15分钟K线数量:24*4=96
    )

    def __init__(self):
        self.order = None
        self.buy_signals = []
        self.sell_signals = []
        self.model = None

        # 技术指标初始化
        self.rsi = bt.indicators.RSI(self.data.close, period=14)
        self.sma_10 = bt.indicators.SimpleMovingAverage(self.data.close, period=10)
        self.sma_30 = bt.indicators.SimpleMovingAverage(self.data.close, period=30)
        self.stoch_k = bt.indicators.StochasticSlow(self.data)
        self.macd = bt.indicators.MACD(self.data.close)
        self.target = TargetIndicator(self.data)

    def next(self):
        # 数据量足够时才执行后续逻辑
        if len(self.data) < self.params.train_window:
            return

        # 每24小时重新训练一次模型
        if len(self.data) % self.params.train_window == 0:
            self.train_model()

        # 模型训练完成后执行预测与交易
        if self.model is not None:
            features = self.prepare_features()
            if features is None:
                return
            prediction = self.model.predict([features])[0]
            signal = 'Buy' if prediction == 1 else 'Sell'
            print(f"AI预测信号: {signal} | 当前价格: {self.data.close[0]}")

            # 买入信号:无持仓时触发
            if signal == 'Buy' and not self.position:
                self.order = self.buy()
                self.buy_signals.append((self.data.datetime.datetime(), self.data.close[0]))
                print(f"买入信号执行,价格: {self.data.close[0]}")

            # 卖出信号:有持仓时触发
            elif signal == 'Sell' and self.position:
                self.order = self.sell()
                self.sell_signals.append((self.data.datetime.datetime(), self.data.close[0]))
                print(f"卖出信号执行,价格: {self.data.close[0]}")

    def prepare_features(self):
        # 提取当前最新的特征值
        features = pd.DataFrame({
            'rsi': [self.rsi[0]],
            'sma_10': [self.sma_10[0]],
            'sma_30': [self.sma_30[0]],
            'stoch_k': [self.stoch_k[0]],
            'macd': [self.macd[0]]
        }).dropna()
        return features.values[0] if not features.empty else None

    def train_model(self):
        # 获取最近24小时的历史数据窗口
        data_window = self.data.get(size=self.params.train_window)
        df = pd.DataFrame({
            'close': [d.close for d in data_window],
            'rsi': [self.rsi[i] for i in range(-self.params.train_window, 0)],
            'sma_10': [self.sma_10[i] for i in range(-self.params.train_window, 0)],
            'sma_30': [self.sma_30[i] for i in range(-self.params.train_window, 0)],
            'stoch_k': [self.stoch_k[i] for i in range(-self.params.train_window, 0)],
            'macd': [self.macd[i] for i in range(-self.params.train_window, 0)]
        }).dropna()

        # 定义目标变量:下一根K线收盘价上涨为1,下跌为0
        df['target'] = np.where(df['close'].shift(-1) > df['close'], 1, 0)
        df = df.dropna()

        if len(df) < 20:
            print("数据量不足,跳过模型训练")
            return

        # 特征工程与模型训练
        features = df[['rsi', 'sma_10', 'sma_30', 'stoch_k', 'macd']]
        target = df['target']

        X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.2, random_state=42)
        model = self.tune_model(X_train, y_train)
        y_pred = model.predict(X_test)
        print(f"模型训练完成,准确率: {accuracy_score(y_test, y_pred)*100:.2f}%")
        self.model = model

    def tune_model(self, features, target):
        param_grid = {
            'n_estimators': [50, 100],
            'max_depth': [None, 10],
            'min_samples_split': [2, 5]
        }
        rf = RandomForestClassifier(random_state=42)
        grid_search = GridSearchCV(estimator=rf, param_grid=param_grid, cv=3, scoring='accuracy', n_jobs=-1)
        grid_search.fit(features, target)
        return grid_search.best_estimator_

    def notify_order(self, order):
        if order.status in [order.Completed]:
            if order.isbuy():
                print(f"买入成交: 价格={order.executed.price:.2f}, 数量={order.executed.size}")
            elif order.issell():
                print(f"卖出成交: 价格={order.executed.price:.2f}, 数量={order.executed.size}")
            self.order = None
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            print("订单被取消/保证金不足/拒绝")
            self.order = None

    def notify_trade(self, trade):
        if trade.isclosed:
            print(f"交易平仓: 总盈亏={trade.pnl:.2f}, 净盈亏={trade.pnlcomm:.2f}")

# 初始化回测引擎
cerebro = bt.Cerebro()
cerebro.addstrategy(AIReversalStrategy)

# 加载历史数据(需确保CSV包含timestamp,open,high,low,close,volume列)
data = pd.read_csv('historical_data.csv', parse_dates=['timestamp'])
data.set_index('timestamp', inplace=True)
datafeed = bt.feeds.PandasData(dataname=data)
cerebro.adddata(datafeed)

# 配置经纪人参数
cerebro.broker.set_cash(1000)
cerebro.broker.setcommission(commission=0.001)

# 添加分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe_ratio')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trade_analyzer')
cerebro.addanalyzer(PerformanceAnalyzer, _name='performance')

# 添加观察者:显示买卖信号与组合价值
cerebro.addobserver(bt.observers.BuySell)
cerebro.addobserver(bt.observers.Value)

# 运行回测
results = cerebro.run()
final_value = cerebro.broker.get_value()
print(f"最终组合价值: {final_value:.2f}")

# 绘制回测结果
cerebro.plot(style='candlestick')

关键修复说明

  1. 回测时间逻辑修正:移除系统时间判断,改用数据长度控制模型训练频率,每96根15分钟K线(对应24小时)重新训练一次模型,匹配策略逻辑
  2. 补全数据获取方法:通过 self.data.get() 获取历史数据窗口,直接从Backtrader指标中提取特征值
  3. 修复语法错误:修正 get(size=len(...)) 的参数格式
  4. 消除前瞻偏差:调整目标变量计算逻辑,避免使用未来数据,统一模型训练与指标的目标定义
  5. 优化训练效率:简化网格搜索参数,减少回测运行时间

内容的提问来源于stack exchange,提问作者Alex Hebra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 08:18:12