package backtest import ( "math" "sig-pub/pkg/types" "sig-pub/pkg/types/decimals" ) type TradeSimulator struct { FeePct float64 // e.g. 0.0005 = 0.05% SlippagePct float64 // e.g. 0.001 = 0.1% TradeId int64 } func NewTradeSimulator(feePct, slippagePct float64) *TradeSimulator { return &TradeSimulator{FeePct: feePct, SlippagePct: slippagePct} } // ExecuteMarket 执行市价单,使用kline信息决定成交价(使用close以及滑点) func (s *TradeSimulator) ExecuteMarket(side types.Side, qty float64, closePrice float64, ts int64) (trd *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 trd = &Trade{Side: side, Qty: qty, Price: base, Fee: fee, Time: ts} s.TradeId++ trd.Id = s.TradeId ok = true return } // ExecuteLimit 简单实现: 如果limit价格被kline的high/low包含则成交 func (s *TradeSimulator) ExecuteLimit(side types.Side, qty float64, limitPx float64, k types.Kline, ts int64) (trd *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 trd = &Trade{Side: side, Qty: qty, Price: px * (1 + s.SlippagePct), Fee: fee, Time: ts} return trd, true } case types.SideShort: if h >= limitPx { px := math.Max(limitPx, decimals.MustToFloat64(k.Open)) fee := math.Abs(px*qty) * s.FeePct trd = &Trade{Side: side, Qty: qty, Price: px * (1 - s.SlippagePct), Fee: fee, Time: ts} return trd, true } } return nil, false }