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" "github.com/bytedance/sonic" ) type TradingPlanBacktester struct { indicatorReg *indicator.IndicatorRegistry sigStrategyReg *strategy.SigStrategyRegistry exchangeClient pb.ExchangeServiceClient account *BacktestAccount sigStrategyType strategy.SigStrategyType sigStrategy strategy.ISigStrategy 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) { // 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 } sigStrategyParam := make(strategy.StrategyParam) if err = sonic.UnmarshalString(plan.SigStrategyParam, &sigStrategyParam); err != nil { return } if err = b.sigStrategy.Init(sigStrategyParam); 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) (err error) { // 交易信号回测器 sigStrategyBacktester := NewSigStrategyBacktester(b.sigStrategyType, b.sigStrategy, b.indicatorReg, b.exchangeClient) onInterval1m, sigTimes := 0, 0 sigStrategyBacktester.SubKline(types.Interval1m, func(interval types.Interval, k types.Kline) (err error) { onInterval1m++ // 检查仓位平仓 return b.closeByKlineInterval1m(k) }) err = sigStrategyBacktester.Backtest(ctx, sr, nil, func(sigSide types.Side, k types.Kline) (err error) { sigTimes++ // 根据交易信号检查仓位平仓 if err = b.closeBySigSingal(sigSide, k); err != nil { return } // 交易下单 return b.onSideSingal(sigSide, k) }) if err != nil { return } // todo 回测报告 var trades []*trade.Trade for _, trade := range b.account.trades { if trade.ClosePrice > 0 { trades = append(trades, trade) } } collect.SortDesc(trades, func(t *trade.Trade) float64 { return t.Pnl }) exposure := b.account.cash + b.account.CurrentExposure() _ = exposure 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 }