package backtest import ( "math" "sig-pub/api/pb" "sig-pub/pkg/types" "sig-pub/pkg/types/decimals" ) type Simulator struct { FeePct float64 // e.g. 0.0005 = 0.05% SlippagePct float64 // e.g. 0.001 = 0.1% } func NewSimulator(feePct, slippagePct float64) *Simulator { return &Simulator{FeePct: feePct, SlippagePct: slippagePct} } // ExecuteMarket 执行市价单,使用kline信息决定成交价(使用close以及滑点) func (s *Simulator) ExecuteMarket(side pb.Side, qty float64, k types.Kline, ts int64) (trade Trade) { // base price use close base := decimals.MustToFloat64(k.Close) slippage := s.SlippagePct if side == pb.Side_SELL { // sell: worse price lower base = base * (1 - slippage) } else { // buy: worse price higher base = base * (1 + slippage) } fee := math.Abs(base*qty) * s.FeePct trade = Trade{Side: side, Qty: qty, Price: base, Fee: fee, Ts: ts} return } // ExecuteLimit 简单实现: 如果limit价格被kline的high/low包含则成交 func (s *Simulator) ExecuteLimit(side pb.Side, qty float64, limitPx float64, k types.Kline, ts int64) (filled bool, trade Trade) { h := decimals.MustToFloat64(k.High) l := decimals.MustToFloat64(k.Low) if side == pb.Side_BUY { // buy limit: filled if low <= price if l <= limitPx { // assume filled at min(limitPx, open) px := math.Min(limitPx, decimals.MustToFloat64(k.Open)) fee := math.Abs(px*qty) * s.FeePct trade = Trade{Side: side, Qty: qty, Price: px * (1 + s.SlippagePct), Fee: fee, Ts: ts} return true, trade } } else if side == pb.Side_SELL { if h >= limitPx { px := math.Max(limitPx, decimals.MustToFloat64(k.Open)) fee := math.Abs(px*qty) * s.FeePct trade = Trade{Side: side, Qty: qty, Price: px * (1 - s.SlippagePct), Fee: fee, Ts: ts} return true, trade } } return false, trade }