package backtest import ( "math" "sig-pub/pkg/data" "sig-pub/pkg/trade" "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% } func NewTradeSimulator(feePct, slippagePct float64) *TradeSimulator { return &TradeSimulator{FeePct: feePct, SlippagePct: slippagePct} } // ExecuteMarket 执行市价单,使用kline信息决定成交价(使用close以及滑点) func (s *TradeSimulator) ExecuteMarket(tradeId int64, symbol string, ticket trade.TradeTicket) (order *trade.TradeOrder) { price := ticket.Price slippage := s.SlippagePct switch ticket.Side { default: panic("invalid trade side") case types.SideLong: // buy: worse price higher price = price * (1 + slippage) case types.SideShort: // sell: worse price lower price = price * (1 - slippage) } ticketQty := decimals.MustToFloat64(ticket.Qty) fee := math.Abs(price*ticketQty) * s.FeePct order = &trade.TradeOrder{ TradeId: tradeId, TradeType: ticket.TradeType, InstId: symbol, Side: ticket.Side, Qty: ticket.Qty, Price: price, Fee: fee, Leverage: ticket.Leverage, Ctime: ticket.Ktime, Status: data.StatusOk, PeakPx: ticket.Price, CloseCause: ticket.Cause, } 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 }