You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

175 lines
4.7 KiB

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
}