package backtest import ( "fmt" "sig-pub/pkg/trade" "sig-pub/pkg/types" "sig-pub/pkg/utils/collect" "sig-pub/pkg/zlog" ) type BacktestTradeAccount struct { trader *TradeSimulator // 下单器 traderOrderId int64 // 订单id递增 cash float64 // 可用资金 positions map[string]*trade.Position // 仓位信息,key: symbol, value: qty(不能持有同一交易产品反方向单, 交易中不能改变杠杆) trades map[int64]*trade.TradeOrder // 所有交易单 openTrades map[string][]int64 // 未平仓交易单,key: symbol, value: tradeId closeTrades []int64 // 平仓交易单 } func NewBacktestTradeAccount(cash float64) *BacktestTradeAccount { return &BacktestTradeAccount{ trader: NewTradeSimulator(0.0005, 0.0008), traderOrderId: 1, cash: cash, positions: make(map[string]*trade.Position), } } // Equity 账户净值 func (a *BacktestTradeAccount) GetCash() float64 { return a.cash } // Equity 账户净值 func (a *BacktestTradeAccount) GetEquity() float64 { return a.cash } // IsSymbolOpen 指定交易对是否有持仓 func (a *BacktestTradeAccount) IsSymbolOpen(symbol string) bool { return a.positions[symbol] != nil } func (a *BacktestTradeAccount) GetOpenTradeInsts() (instIds []string) { for instId := range a.openTrades { instIds = append(instIds, instId) } return } func (a *BacktestTradeAccount) GetOpenTrades(instId string) []*trade.TradeOrder { var trades []*trade.TradeOrder for _, tradeId := range a.openTrades[instId] { trades = append(trades, a.trades[tradeId]) } return trades } // GetSymbolOpenPosition 获取指定交易对的仓位信息 func (a *BacktestTradeAccount) GetSymbolOpenPosition(symbol string) *trade.Position { return a.positions[symbol] } // MarketOrder 市价下单 func (a *BacktestTradeAccount) MarketOrder(ticket trade.TradeTicket) (order *trade.TradeOrder, err error) { instId := ticket.InstId if pos, ok := a.positions[instId]; ok { // 检查反方向单 if pos.Side != ticket.Side { err = fmt.Errorf("cannot open opposite side position") return } // 同方向杠杆倍数 if pos.Leverage != ticket.Leverage { err = fmt.Errorf("cannot change leverage on existing position") return } } cost, order := a.trader.ExecuteMarket(a.traderOrderId, instId, ticket) if cost > a.cash { err = fmt.Errorf("insufficient cash") return } a.traderOrderId++ a.cash -= cost a.trades[order.TradeId] = order a.openTrades[instId] = append(a.openTrades[instId], order.TradeId) // open/add position pos, ok := a.positions[instId] if !ok { pos = &trade.Position{ InstId: order.InstId, Side: order.Side, Qty: order.Qty, Leverage: order.Leverage, EntryPx: order.Price, EntryTs: order.Ctime, PeakPx: order.Price, } a.positions[instId] = pos } else { totalQty := pos.Qty + order.Qty pos.EntryPx = (pos.EntryPx*pos.Qty + order.Price*order.Qty) / totalQty pos.Qty = totalQty if pos.Side == types.SideLong && order.Price < pos.PeakPx { pos.PeakPx = order.Price } if pos.Side == types.SideShort && order.Price > pos.PeakPx { pos.PeakPx = order.Price } } return } // CloseTradeOrder 订单订单平仓 func (a *BacktestTradeAccount) CloseTradeOrder(ticket trade.TradeTicket) (err error) { instId := ticket.InstId // 检查仓位 pos, ok := a.positions[instId] if !ok { err = fmt.Errorf("no open position for symbol: %s", instId) return } if pos.Side == ticket.Side { err = fmt.Errorf("cannot close position with same side trade") return } if ticket.Qty > pos.Qty { err = fmt.Errorf("close quantity exceeds position quantity") return } cost, order := a.trader.ExecuteMarket(a.traderOrderId, instId, ticket) a.traderOrderId++ a.cash += cost a.trades[order.TradeId] = order a.closeTrades = append(a.closeTrades, order.TradeId) if len(ticket.TradesId) > 0 { if trades, ok := a.openTrades[instId]; ok { // remove open trade order removes := collect.Remove(&trades, func(tradeId int64) bool { return collect.In(tradeId, ticket.TradesId...) }) a.openTrades[instId] = trades if removes != len(ticket.TradesId) { zlog.Warningf("close ticket tradeId not exists: instId=%s, tradeId=%#v", ticket.InstId, ticket.TradesId) } } if len(a.openTrades[instId]) == 0 { delete(a.openTrades, instId) } } // update position pos.Qty -= ticket.Qty if pos.Qty <= 0 { delete(a.positions, instId) if _, ok := a.openTrades[instId]; ok { zlog.Warningf("close all position trades: %s", instId) } } // todo 平仓单统计 return } // CloseOpsition 仓位平仓 func (a *BacktestTradeAccount) CloseOpsition(pos *trade.Position) (err error) { return }