bbgo: refactor trade collector using position interface

This commit is contained in:
austin362667 2022-05-30 15:15:44 +08:00
parent 8652b4e043
commit a5e1ae4867
3 changed files with 170 additions and 6 deletions

View File

@ -18,17 +18,17 @@ type TradeCollector struct {
tradeStore *TradeStore tradeStore *TradeStore
tradeC chan types.Trade tradeC chan types.Trade
position *types.Position position types.PositionInterface
orderStore *OrderStore orderStore *OrderStore
doneTrades map[types.TradeKey]struct{} doneTrades map[types.TradeKey]struct{}
recoverCallbacks []func(trade types.Trade) recoverCallbacks []func(trade types.Trade)
tradeCallbacks []func(trade types.Trade, profit, netProfit fixedpoint.Value) tradeCallbacks []func(trade types.Trade, profit, netProfit fixedpoint.Value)
positionUpdateCallbacks []func(position *types.Position) positionUpdateCallbacks []func(position types.PositionInterface)
profitCallbacks []func(trade types.Trade, profit, netProfit fixedpoint.Value) profitCallbacks []func(trade types.Trade, profit, netProfit fixedpoint.Value)
} }
func NewTradeCollector(symbol string, position *types.Position, orderStore *OrderStore) *TradeCollector { func NewTradeCollector(symbol string, position types.PositionInterface, orderStore *OrderStore) *TradeCollector {
return &TradeCollector{ return &TradeCollector{
Symbol: symbol, Symbol: symbol,
orderSig: sigchan.New(1), orderSig: sigchan.New(1),
@ -47,7 +47,7 @@ func (c *TradeCollector) OrderStore() *OrderStore {
} }
// Position returns the position used by the trade collector // Position returns the position used by the trade collector
func (c *TradeCollector) Position() *types.Position { func (c *TradeCollector) Position() types.PositionInterface {
return c.position return c.position
} }

View File

@ -27,11 +27,11 @@ func (c *TradeCollector) EmitTrade(trade types.Trade, profit fixedpoint.Value, n
} }
} }
func (c *TradeCollector) OnPositionUpdate(cb func(position *types.Position)) { func (c *TradeCollector) OnPositionUpdate(cb func(position types.PositionInterface)) {
c.positionUpdateCallbacks = append(c.positionUpdateCallbacks, cb) c.positionUpdateCallbacks = append(c.positionUpdateCallbacks, cb)
} }
func (c *TradeCollector) EmitPositionUpdate(position *types.Position) { func (c *TradeCollector) EmitPositionUpdate(position types.PositionInterface) {
for _, cb := range c.positionUpdateCallbacks { for _, cb := range c.positionUpdateCallbacks {
cb(position) cb(position)
} }

View File

@ -29,6 +29,10 @@ type PositionRisk struct {
LiquidationPrice fixedpoint.Value `json:"liquidationPrice"` LiquidationPrice fixedpoint.Value `json:"liquidationPrice"`
} }
type PositionInterface interface {
AddTrade(td Trade) (profit fixedpoint.Value, netProfit fixedpoint.Value, madeProfit bool)
}
type Position struct { type Position struct {
Symbol string `json:"symbol" db:"symbol"` Symbol string `json:"symbol" db:"symbol"`
BaseCurrency string `json:"baseCurrency" db:"base"` BaseCurrency string `json:"baseCurrency" db:"base"`
@ -453,3 +457,163 @@ func (p *Position) AddTrade(td Trade) (profit fixedpoint.Value, netProfit fixedp
return fixedpoint.Zero, fixedpoint.Zero, false return fixedpoint.Zero, fixedpoint.Zero, false
} }
type PositionStack struct {
*Position
Stack []*Position
}
func (stack *PositionStack) Push(pos *Position) *PositionStack {
stack.Position = pos
stack.Stack = append(stack.Stack, pos)
return stack
}
func (stack *PositionStack) Pop() *PositionStack {
if len(stack.Stack) < 1 {
return nil
}
stack.Position = stack.Stack[len(stack.Stack)-1]
stack.Stack = stack.Stack[:len(stack.Stack)-1]
return stack
}
func NewPositionStackFromMarket(market Market) *PositionStack {
pos := &Position{
Symbol: market.Symbol,
BaseCurrency: market.BaseCurrency,
QuoteCurrency: market.QuoteCurrency,
Market: market,
TotalFee: make(map[string]fixedpoint.Value),
}
return &PositionStack{
Position: pos,
Stack: []*Position{pos},
}
}
func (p *PositionStack) AddTrade(td Trade) (profit fixedpoint.Value, netProfit fixedpoint.Value, madeProfit bool) {
price := td.Price
quantity := td.Quantity
quoteQuantity := td.QuoteQuantity
fee := td.Fee
// calculated fee in quote (some exchange accounts may enable platform currency fee discount, like BNB)
// convert platform fee token into USD values
var feeInQuote fixedpoint.Value = fixedpoint.Zero
switch td.FeeCurrency {
case p.BaseCurrency:
quantity = quantity.Sub(fee)
case p.QuoteCurrency:
quoteQuantity = quoteQuantity.Sub(fee)
default:
if p.ExchangeFeeRates != nil {
if exchangeFee, ok := p.ExchangeFeeRates[td.Exchange]; ok {
if td.IsMaker {
feeInQuote = feeInQuote.Add(exchangeFee.MakerFeeRate.Mul(quoteQuantity))
} else {
feeInQuote = feeInQuote.Add(exchangeFee.TakerFeeRate.Mul(quoteQuantity))
}
}
} else if p.FeeRate != nil {
if td.IsMaker {
feeInQuote = feeInQuote.Add(p.FeeRate.MakerFeeRate.Mul(quoteQuantity))
} else {
feeInQuote = feeInQuote.Add(p.FeeRate.TakerFeeRate.Mul(quoteQuantity))
}
}
}
p.Lock()
defer p.Unlock()
// update changedAt field before we unlock in the defer func
defer func() {
p.ChangedAt = td.Time.Time()
}()
p.addTradeFee(td)
// Base > 0 means we're in long position
// Base < 0 means we're in short position
switch td.Side {
case SideTypeBuy:
if p.Base.Sign() < 0 {
// convert short position to long position
if p.Base.Add(quantity).Sign() > 0 {
profit = p.AverageCost.Sub(price).Mul(p.Base.Neg())
netProfit = p.ApproximateAverageCost.Sub(price).Mul(p.Base.Neg()).Sub(feeInQuote)
p.Base = p.Base.Add(quantity)
p.Quote = p.Quote.Sub(quoteQuantity)
p.AverageCost = price
p.ApproximateAverageCost = price
p.AccumulatedProfit = p.AccumulatedProfit.Add(profit)
return profit, netProfit, true
} else {
// covering short position
p.Base = p.Base.Add(quantity)
p.Quote = p.Quote.Sub(quoteQuantity)
profit = p.AverageCost.Sub(price).Mul(quantity)
netProfit = p.ApproximateAverageCost.Sub(price).Mul(quantity).Sub(feeInQuote)
p.AccumulatedProfit = p.AccumulatedProfit.Add(profit)
return profit, netProfit, true
}
}
divisor := p.Base.Add(quantity)
p.ApproximateAverageCost = p.ApproximateAverageCost.Mul(p.Base).
Add(quoteQuantity).
Add(feeInQuote).
Div(divisor)
p.AverageCost = p.AverageCost.Mul(p.Base).Add(quoteQuantity).Div(divisor)
p.Base = p.Base.Add(quantity)
p.Quote = p.Quote.Sub(quoteQuantity)
return fixedpoint.Zero, fixedpoint.Zero, false
case SideTypeSell:
if p.Base.Sign() > 0 {
// convert long position to short position
if p.Base.Compare(quantity) < 0 {
profit = price.Sub(p.AverageCost).Mul(p.Base)
netProfit = price.Sub(p.ApproximateAverageCost).Mul(p.Base).Sub(feeInQuote)
p.Base = p.Base.Sub(quantity)
p.Quote = p.Quote.Add(quoteQuantity)
p.AverageCost = price
p.ApproximateAverageCost = price
p.AccumulatedProfit = p.AccumulatedProfit.Add(profit)
return profit, netProfit, true
} else {
p.Base = p.Base.Sub(quantity)
p.Quote = p.Quote.Add(quoteQuantity)
profit = price.Sub(p.AverageCost).Mul(quantity)
netProfit = price.Sub(p.ApproximateAverageCost).Mul(quantity).Sub(feeInQuote)
p.AccumulatedProfit = p.AccumulatedProfit.Add(profit)
return profit, netProfit, true
}
}
// handling short position, since Base here is negative we need to reverse the sign
divisor := quantity.Sub(p.Base)
p.ApproximateAverageCost = p.ApproximateAverageCost.Mul(p.Base.Neg()).
Add(quoteQuantity).
Sub(feeInQuote).
Div(divisor)
p.AverageCost = p.AverageCost.Mul(p.Base.Neg()).
Add(quoteQuantity).
Div(divisor)
p.Base = p.Base.Sub(quantity)
p.Quote = p.Quote.Add(quoteQuantity)
return fixedpoint.Zero, fixedpoint.Zero, false
}
return fixedpoint.Zero, fixedpoint.Zero, false
}