You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

190 lines
5.2 KiB

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
}