package backtest import ( "math" "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% TradeId int64 } func NewSimulator(feePct, slippagePct float64) *Simulator { return &Simulator{FeePct: feePct, SlippagePct: slippagePct} } // ExecuteMarket 执行市价单,使用kline信息决定成交价(使用close以及滑点) func (s *Simulator) ExecuteMarket(side types.Side, qty float64, closePrice float64, ts int64) (trade *Trade, ok bool) { // base price use close base := closePrice slippage := s.SlippagePct switch side { case types.SideLong: // buy: worse price higher base = base * (1 + slippage) case types.SideShort: // sell: worse price lower base = base * (1 - slippage) default: return } fee := math.Abs(base*qty) * s.FeePct trade = &Trade{Side: side, Qty: qty, Price: base, Fee: fee, Ts: ts} s.TradeId++ trade.Id = s.TradeId ok = true return } // ExecuteLimit 简单实现: 如果limit价格被kline的high/low包含则成交 func (s *Simulator) ExecuteLimit(side types.Side, qty float64, limitPx float64, k types.Kline, ts int64) (trade Trade, filled bool) { h := decimals.MustToFloat64(k.High) l := decimals.MustToFloat64(k.Low) switch side { case types.SideLong: // 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 trade, true } case types.SideShort: 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 trade, true } } return trade, false }