package backtest import ( "context" "errors" "fmt" "sig-pub/api/pb" "sig-pub/pkg/data/entity" "sig-pub/pkg/indicator" "sig-pub/pkg/strategy" "sig-pub/pkg/trade" "sig-pub/pkg/types" "sig-pub/pkg/utils/collect" "sig-pub/pkg/zlog" "time" "github.com/bytedance/sonic" ) // todo 将backtest独立成单独服务横向扩展 type TradingPlanBacktester struct { indicatorReg *indicator.IndicatorRegistry sigStrategyReg *strategy.SigStrategyRegistry exchangeClient pb.ExchangeServiceClient plan entity.TradePlan account *BacktestAccount sigStrategyType strategy.SigStrategyType sigStrategy strategy.ISigStrategy sigStrategyInput types.Input closeStrategy *trade.CloseStrategy riskStrategy *trade.RiskStrategy tradeStrategy *trade.TradeStrategy } func NewTradingPlanBacktester(indicatorReg *indicator.IndicatorRegistry, sigStrategyReg *strategy.SigStrategyRegistry, exchangeClient pb.ExchangeServiceClient) *TradingPlanBacktester { return &TradingPlanBacktester{ indicatorReg: indicatorReg, sigStrategyReg: sigStrategyReg, exchangeClient: exchangeClient, } } func (b *TradingPlanBacktester) Init(cash float64, plan entity.TradePlan) (err error) { b.plan = plan // trade account simulator := NewTradeSimulator(0.0005, 0.0008) b.account = NewBacktestAccount(cash, simulator) // sig strategy ok := false b.sigStrategyType, b.sigStrategy, ok = b.sigStrategyReg.NewSigStrategy(plan.SigStrategy) if !ok { err = fmt.Errorf("sig strategy not exists %s", plan.SigStrategy) return } if err = sonic.UnmarshalString(plan.SigStrategyParam, &b.sigStrategyInput); err != nil { return } if err = b.sigStrategy.Init(b.sigStrategyInput); err != nil { return } closeStrategyParam, tradeStrategyParam, riskStrategyParam := new(trade.CloseStrategyParam), new(trade.TradeStrategyParam), new(trade.RiskStrategyParam) if err = sonic.UnmarshalString(plan.CloseStrategyParam, closeStrategyParam); err != nil { return } if err = sonic.UnmarshalString(plan.TradeStrategyParam, tradeStrategyParam); err != nil { return } if err = sonic.UnmarshalString(plan.RiskStrategyParam, riskStrategyParam); err != nil { return } // 平仓策略 b.closeStrategy, err = trade.NewCloseStrategy(*closeStrategyParam) if err != nil { return } // 风险管理策略 b.riskStrategy, err = trade.NewRiskStrategy(*riskStrategyParam) if err != nil { return } // 交易策略 b.tradeStrategy, err = trade.NewTradeStrategy(*tradeStrategyParam) if err != nil { return } return } // 核心引擎,模拟交易、持仓跟踪、费用计算 func (b *TradingPlanBacktester) Backtest(ctx context.Context, sr *pb.SeriesRange) (test *BacktestTradingPlan, err error) { test = &BacktestTradingPlan{ Id: time.Now().Unix(), UserId: 10001, PlanId: b.plan.Id, InstId: b.plan.InstId, Exchange: pb.ExchangeType(b.plan.Exchange), Interval: b.plan.Interval, SeriesBefore: sr.Before, SeriesAfter: sr.After, Ctime: time.Now().UnixMilli(), Cash: b.account.cash, } // 交易信号回测器 sigStrategyBacktester := NewSigStrategyBacktester(b.sigStrategyType, b.sigStrategy, b.indicatorReg, b.exchangeClient) sigStrategyBacktester.SubKline(sr.InstId, types.Interval1m, func(instId string, interval types.Interval, k types.Kline) (err error) { // 检查仓位平仓 return b.closeByKlineInterval1m(k) }) iiks := types.NewInstanceIntervalKlineSeries() err = sigStrategyBacktester.Backtest(ctx, b.sigStrategyInput, sr, iiks, func(sigSide types.Side, k types.Kline) (err error) { test.Singals++ // 根据交易信号检查仓位平仓 if err = b.closeBySigSingal(sigSide, k); err != nil { return } // 交易下单 return b.onSideSingal(sigSide, k) }) if err != nil { return } // 读最新的k线 kSeries := iiks.Get(sr.InstId, types.Interval(sr.Interval)) lastCandle, err := kSeries.Get(0) if err != nil { return } // 关闭所有未平仓仓位 b.forceCloseAllHoldingPosition(lastCandle) // 回测结果 test.Etime = time.Now().UnixMilli() test.EndCash = b.account.cash test.Profit = b.account.profit test.TotalTrades = len(b.account.trades) test.WinningTrades = b.account.winningTrades test.LosingTrades = b.account.losingTrades test.Fee = b.account.fee // trades var trades []*Trade for _, trade := range b.account.trades { trade.BacktestId = test.Id trade.Ctime = test.Ctime trades = append(trades, trade) } collect.SortAsc(trades, func(t *Trade) int64 { return t.Id }) test.Trades = trades // 最大回撤 drawdown := b.account.maxDrawdown test.MaxDrawdown = max((drawdown[0]-drawdown[1])/drawdown[0], (drawdown[2]-drawdown[3])/drawdown[2]) return } // forceCloseAllHoldingPosition 关闭所有未平仓仓位 func (b *TradingPlanBacktester) forceCloseAllHoldingPosition(k types.Kline) (err error) { for _, pos := range b.account.positions { err = b.account.ClosePosition(pos, k, trade.CauseCloseForced) if err != nil { return } } return } // closeByKlineInterval1m k线更新时检查平仓 func (b *TradingPlanBacktester) closeByKlineInterval1m(k types.Kline) (err error) { var posErrs []error positions := b.account.OpenPositions() for _, pos := range positions { closePos, cause := b.closeStrategy.OnKline(k, pos) if closePos { errc := b.account.ClosePosition(pos, k, cause) if errc != nil { posErrs = append(posErrs, errc) zlog.Errorf("close position error: k=%#v, err=%v", k, errc) } } } if len(posErrs) > 0 { err = errors.Join(posErrs...) return } return } // closeBySigSingal 交易信号出现时检查平仓 func (b *TradingPlanBacktester) closeBySigSingal(sigSide types.Side, kline types.Kline) (err error) { var posErrs []error positions := b.account.OpenPositions() for _, pos := range positions { closePos, cause := b.closeStrategy.OnSigStrategySingal(sigSide, pos) if closePos { errc := b.account.ClosePosition(pos, kline, cause) if errc != nil { posErrs = append(posErrs, errc) zlog.Error("close position error: ", errc) } } } if len(posErrs) > 0 { err = errors.Join(posErrs...) return } return } // onSideSingal 出现买卖信号 func (b *TradingPlanBacktester) onSideSingal(sigSide types.Side, k types.Kline) (err error) { // 买卖信号交易风险分析 doTrade, causes, err := b.riskStrategy.SigRiskAnalyze(sigSide) if err != nil { return } if !doTrade { _ = causes // todo 记录信号不交易原因分析 log db analyze return } tradeArg, err := b.tradeStrategy.SigTrade(sigSide, k) if err != nil { return } ok, cause, err := b.account.TradeOrder(tradeArg) if err != nil { return } if !ok { _ = cause // todo 记录不交易原因 } return }