|
|
|
@ -8,6 +8,7 @@ import ( |
|
|
|
"sig-pub/internal/trading/sig" |
|
|
|
"sig-pub/internal/trading/sig" |
|
|
|
"sig-pub/pkg/indicator" |
|
|
|
"sig-pub/pkg/indicator" |
|
|
|
"sig-pub/pkg/strategy" |
|
|
|
"sig-pub/pkg/strategy" |
|
|
|
|
|
|
|
"sig-pub/pkg/trade" |
|
|
|
"sig-pub/pkg/types" |
|
|
|
"sig-pub/pkg/types" |
|
|
|
"sig-pub/pkg/types/decimals" |
|
|
|
"sig-pub/pkg/types/decimals" |
|
|
|
"sig-pub/pkg/utils/times" |
|
|
|
"sig-pub/pkg/utils/times" |
|
|
|
@ -20,10 +21,22 @@ type Backtest struct { |
|
|
|
exchangeClient pb.ExchangeServiceClient |
|
|
|
exchangeClient pb.ExchangeServiceClient |
|
|
|
indReg *indicator.IndicatorRegistry |
|
|
|
indReg *indicator.IndicatorRegistry |
|
|
|
sigStrategyReg *strategy.SigStrategyRegistry |
|
|
|
sigStrategyReg *strategy.SigStrategyRegistry |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
account *Account |
|
|
|
|
|
|
|
riskStrategy *trade.RiskStrategy |
|
|
|
|
|
|
|
closeManager *CloseManager |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func NewBacktest(exchangeClient pb.ExchangeServiceClient, indReg *indicator.IndicatorRegistry, sigStrategyReg *strategy.SigStrategyRegistry) *Backtest { |
|
|
|
func NewBacktest(exchangeClient pb.ExchangeServiceClient, indReg *indicator.IndicatorRegistry, sigStrategyReg *strategy.SigStrategyRegistry) *Backtest { |
|
|
|
return &Backtest{exchangeClient: exchangeClient, indReg: indReg} |
|
|
|
account := NewAccount(10000, NewSimulator(0.0005, 0.0008)) |
|
|
|
|
|
|
|
cm := NewCloseManager(0.02, 0.03) |
|
|
|
|
|
|
|
return &Backtest{ |
|
|
|
|
|
|
|
exchangeClient: exchangeClient, |
|
|
|
|
|
|
|
indReg: indReg, |
|
|
|
|
|
|
|
riskStrategy: trade.NewRiskStrategy(), |
|
|
|
|
|
|
|
account: account, |
|
|
|
|
|
|
|
closeManager: cm, |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (b *Backtest) RunTradingPlan(ctx context.Context, tradingPlan *sig.TradingPlan, stime, etime int64, sigKlineSeries *sig.KlineSeries) (err error) { |
|
|
|
func (b *Backtest) RunTradingPlan(ctx context.Context, tradingPlan *sig.TradingPlan, stime, etime int64, sigKlineSeries *sig.KlineSeries) (err error) { |
|
|
|
@ -61,6 +74,7 @@ func (b *Backtest) RunTradingPlan(ctx context.Context, tradingPlan *sig.TradingP |
|
|
|
|
|
|
|
|
|
|
|
recvTimes, total := 0, 0 |
|
|
|
recvTimes, total := 0, 0 |
|
|
|
watch := times.NewWatch() |
|
|
|
watch := times.NewWatch() |
|
|
|
|
|
|
|
var lastK *types.Kline |
|
|
|
for { |
|
|
|
for { |
|
|
|
msg, err0 := stream.Recv() |
|
|
|
msg, err0 := stream.Recv() |
|
|
|
if err0 == io.EOF { |
|
|
|
if err0 == io.EOF { |
|
|
|
@ -75,7 +89,7 @@ func (b *Backtest) RunTradingPlan(ctx context.Context, tradingPlan *sig.TradingP |
|
|
|
for _, k := range msg.Klines { |
|
|
|
for _, k := range msg.Klines { |
|
|
|
kline := new(types.Kline) |
|
|
|
kline := new(types.Kline) |
|
|
|
kline.ParsePBKline(seriesRange.Exchange, k) |
|
|
|
kline.ParsePBKline(seriesRange.Exchange, k) |
|
|
|
|
|
|
|
lastK = kline |
|
|
|
if lastTs, serial := sigKlineSeries.Update(kline); !serial { |
|
|
|
if lastTs, serial := sigKlineSeries.Update(kline); !serial { |
|
|
|
err = fmt.Errorf("kline not series: %s(%s), interval=%s, lastTs=%d", instId, exchange, interval, lastTs) |
|
|
|
err = fmt.Errorf("kline not series: %s(%s), interval=%s, lastTs=%d", instId, exchange, interval, lastTs) |
|
|
|
return |
|
|
|
return |
|
|
|
@ -85,19 +99,56 @@ func (b *Backtest) RunTradingPlan(ctx context.Context, tradingPlan *sig.TradingP |
|
|
|
continue |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
signalSide := tradingPlan.Update(strategy.StrategyTypeSig) |
|
|
|
// 平仓策略
|
|
|
|
switch signalSide { |
|
|
|
b.closeManager.OnKline(*kline, b.account) |
|
|
|
case types.SideBuy, types.SideSell: |
|
|
|
|
|
|
|
k, _ := sigKlineSeries.Get(0) |
|
|
|
sigSide := tradingPlan.Update(strategy.StrategyTypeSig) |
|
|
|
_ = k |
|
|
|
if sigSide.IsValid() { |
|
|
|
// zlog.Infof("sigSide: ts=%d, %s", k.Ts, sigSide)
|
|
|
|
b.onSigSideSignal(sigSide, *kline) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// build result
|
|
|
|
|
|
|
|
res := &BacktestResult{} |
|
|
|
|
|
|
|
res.StartTs = 0 |
|
|
|
|
|
|
|
res.EndTs = 0 |
|
|
|
|
|
|
|
acct := b.account |
|
|
|
|
|
|
|
res.Trades = acct.Trades |
|
|
|
|
|
|
|
for _, p := range acct.Positions { |
|
|
|
|
|
|
|
res.Positions = append(res.Positions, *p) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
res.Cash = acct.Cash |
|
|
|
|
|
|
|
// estimate equity using last close price
|
|
|
|
|
|
|
|
if lastK != nil { |
|
|
|
|
|
|
|
last := decimals.MustToFloat64(lastK.Close) |
|
|
|
|
|
|
|
equity := acct.Cash |
|
|
|
|
|
|
|
// naive mark-to-market of positions
|
|
|
|
|
|
|
|
for _, p := range acct.Positions { |
|
|
|
|
|
|
|
if p.Side == types.SideBuy { |
|
|
|
|
|
|
|
equity += (last - p.EntryPx) * p.Qty |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
equity += (p.EntryPx - last) * p.Qty |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
res.Equity = equity |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
zlog.Debugf("recv=%d, total=%d, use %s, ret=%#v", recvTimes, total, watch.ElapsedFmt("."), res) |
|
|
|
|
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
zlog.Debugf("recv=%d, total=%d, use %s", recvTimes, total, watch.ElapsedFmt(".")) |
|
|
|
// onSigSideSignal 交易策略发出交易信号
|
|
|
|
|
|
|
|
func (b *Backtest) onSigSideSignal(sigSide types.Side, k types.Kline) { |
|
|
|
|
|
|
|
side := b.riskStrategy.SideAssess(sigSide) |
|
|
|
|
|
|
|
if !side.IsValid() { |
|
|
|
|
|
|
|
zlog.Debugf("risk strategy filter sig side: %s", sigSide.String()) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
zlog.Debugf("apply market order: ts=%d, side=%s", k.Ts, side.String()) |
|
|
|
|
|
|
|
b.account.ApplyMarketOrder(side, 0.01, k.Ts, k) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
b.closeManager.CloseBySignal(types.SideBuy, b.account, k) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Run 执行回测
|
|
|
|
// Run 执行回测
|
|
|
|
// seriesRange: 回测的交易产品/周期/时间区间
|
|
|
|
// seriesRange: 回测的交易产品/周期/时间区间
|
|
|
|
@ -178,9 +229,9 @@ func (b *Backtest) Run0(ctx context.Context, seriesRange *pb.SeriesRange, sigStr |
|
|
|
if side == types.SideBuy || side == types.SideSell { |
|
|
|
if side == types.SideBuy || side == types.SideSell { |
|
|
|
// signal-based close: close opposite positions first
|
|
|
|
// signal-based close: close opposite positions first
|
|
|
|
if cm != nil { |
|
|
|
if cm != nil { |
|
|
|
cm.CloseBySignal(pb.Side_BUY, acct, klines[i]) |
|
|
|
cm.CloseBySignal(types.SideBuy, acct, klines[i]) |
|
|
|
} |
|
|
|
} |
|
|
|
if _, ok := acct.ApplyMarketOrder(pb.Side_BUY, qty, klines[i].Ts, klines[i]); ok { |
|
|
|
if _, ok := acct.ApplyMarketOrder(types.SideBuy, qty, klines[i].Ts, klines[i]); ok { |
|
|
|
// trade recorded
|
|
|
|
// trade recorded
|
|
|
|
} else { |
|
|
|
} else { |
|
|
|
zlog.Debugf("order rejected or insufficient cash at ts=%d", klines[i].Ts) |
|
|
|
zlog.Debugf("order rejected or insufficient cash at ts=%d", klines[i].Ts) |
|
|
|
@ -202,7 +253,7 @@ func (b *Backtest) Run0(ctx context.Context, seriesRange *pb.SeriesRange, sigStr |
|
|
|
equity := acct.Cash |
|
|
|
equity := acct.Cash |
|
|
|
// naive mark-to-market of positions
|
|
|
|
// naive mark-to-market of positions
|
|
|
|
for _, p := range acct.Positions { |
|
|
|
for _, p := range acct.Positions { |
|
|
|
if p.Side == pb.Side_BUY { |
|
|
|
if p.Side == types.SideBuy { |
|
|
|
equity += (last - p.EntryPx) * p.Qty |
|
|
|
equity += (last - p.EntryPx) * p.Qty |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
equity += (p.EntryPx - last) * p.Qty |
|
|
|
equity += (p.EntryPx - last) * p.Qty |
|
|
|
|