bbgo_origin/pkg/bbgo/position.go

251 lines
6.6 KiB
Go
Raw Normal View History

2021-01-20 08:08:14 +00:00
package bbgo
import (
2021-02-16 08:40:11 +00:00
"fmt"
"sync"
"time"
2021-02-16 08:40:11 +00:00
2021-01-20 08:08:14 +00:00
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
"github.com/slack-go/slack"
2021-01-20 08:08:14 +00:00
)
type ExchangeFee struct {
MakerFeeRate fixedpoint.Value
TakerFeeRate fixedpoint.Value
}
2021-01-20 08:08:14 +00:00
type Position struct {
Symbol string `json:"symbol"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
2021-10-17 14:23:09 +00:00
Market types.Market `json:"market"`
2021-01-20 08:15:34 +00:00
Base fixedpoint.Value `json:"base"`
Quote fixedpoint.Value `json:"quote"`
AverageCost fixedpoint.Value `json:"averageCost"`
// ApproximateAverageCost adds the computed fee in quote in the average cost
// This is used for calculating net profit
ApproximateAverageCost fixedpoint.Value `json:"approximateAverageCost"`
2021-12-04 18:16:48 +00:00
FeeRate *ExchangeFee `json:"feeRate,omitempty"`
ExchangeFeeRates map[types.ExchangeName]ExchangeFee `json:"exchangeFeeRates"`
sync.Mutex
2021-01-20 08:08:14 +00:00
}
2021-06-24 07:38:22 +00:00
func NewPositionFromMarket(market types.Market) *Position {
return &Position{
Symbol: market.Symbol,
BaseCurrency: market.BaseCurrency,
QuoteCurrency: market.QuoteCurrency,
2021-10-17 14:23:09 +00:00
Market: market,
2021-06-24 07:38:22 +00:00
}
}
func NewPosition(symbol, base, quote string) *Position {
return &Position{
Symbol: symbol,
BaseCurrency: base,
QuoteCurrency: quote,
}
}
2021-05-20 16:08:04 +00:00
func (p *Position) Reset() {
p.Base = 0
p.Quote = 0
p.AverageCost = 0
}
2021-12-04 18:16:48 +00:00
func (p *Position) SetFeeRate(exchangeFee ExchangeFee) {
p.FeeRate = &exchangeFee
}
func (p *Position) SetExchangeFeeRate(ex types.ExchangeName, exchangeFee ExchangeFee) {
if p.ExchangeFeeRates == nil {
p.ExchangeFeeRates = make(map[types.ExchangeName]ExchangeFee)
}
p.ExchangeFeeRates[ex] = exchangeFee
}
func (p *Position) SlackAttachment() slack.Attachment {
2021-05-15 17:16:03 +00:00
p.Lock()
averageCost := p.AverageCost
base := p.Base
quote := p.Quote
p.Unlock()
var posType = ""
var color = ""
if p.Base == 0 {
color = "#cccccc"
posType = "Closed"
} else if p.Base > 0 {
posType = "Long"
color = "#228B22"
} else if p.Base < 0 {
posType = "Short"
color = "#DC143C"
}
title := util.Render(posType+` Position {{ .Symbol }} `, p)
return slack.Attachment{
// Pretext: "",
// Text: text,
Title: title,
Color: color,
Fields: []slack.AttachmentField{
2021-05-15 17:16:03 +00:00
{Title: "Average Cost", Value: util.FormatFloat(averageCost.Float64(), 2), Short: true},
{Title: p.BaseCurrency, Value: util.FormatFloat(base.Float64(), 4), Short: true},
{Title: p.QuoteCurrency, Value: util.FormatFloat(quote.Float64(), 2)},
},
Footer: util.Render("update time {{ . }}", time.Now().Format(time.RFC822)),
// FooterIcon: "",
}
}
func (p *Position) PlainText() string {
2021-05-15 16:45:08 +00:00
return fmt.Sprintf("Position %s: average cost = %f, base = %f, quote = %f",
p.Symbol,
p.AverageCost.Float64(),
p.Base.Float64(),
p.Quote.Float64(),
)
}
2021-05-15 02:02:04 +00:00
func (p *Position) String() string {
return fmt.Sprintf("POSITION %s: average cost = %f, base = %f, quote = %f",
2021-02-16 08:40:11 +00:00
p.Symbol,
p.AverageCost.Float64(),
p.Base.Float64(),
p.Quote.Float64(),
)
}
func (p *Position) BindStream(stream types.Stream) {
stream.OnTradeUpdate(func(trade types.Trade) {
2021-01-20 09:37:23 +00:00
if p.Symbol == trade.Symbol {
p.AddTrade(trade)
}
})
}
2021-05-22 09:17:37 +00:00
func (p *Position) AddTrades(trades []types.Trade) (fixedpoint.Value, fixedpoint.Value, bool) {
var totalProfitAmount, totalNetProfit fixedpoint.Value
for _, trade := range trades {
2021-05-22 09:17:37 +00:00
if profit, netProfit, madeProfit := p.AddTrade(trade); madeProfit {
totalProfitAmount += profit
totalNetProfit += netProfit
}
}
2021-05-22 09:17:37 +00:00
return totalProfitAmount, totalNetProfit, totalProfitAmount != 0
}
2021-05-22 09:17:37 +00:00
func (p *Position) AddTrade(t types.Trade) (profit fixedpoint.Value, netProfit fixedpoint.Value, madeProfit bool) {
2021-01-20 08:08:14 +00:00
price := fixedpoint.NewFromFloat(t.Price)
quantity := fixedpoint.NewFromFloat(t.Quantity)
2021-01-20 16:49:01 +00:00
quoteQuantity := fixedpoint.NewFromFloat(t.QuoteQuantity)
fee := fixedpoint.NewFromFloat(t.Fee)
// calculated fee in quote (some exchange accounts may enable platform currency fee discount, like BNB)
var feeInQuote fixedpoint.Value = 0
2021-01-20 16:49:01 +00:00
switch t.FeeCurrency {
case p.BaseCurrency:
quantity -= fee
case p.QuoteCurrency:
quoteQuantity -= fee
default:
if p.ExchangeFeeRates != nil {
if exchangeFee, ok := p.ExchangeFeeRates[t.Exchange]; ok {
if t.IsMaker {
feeInQuote += exchangeFee.MakerFeeRate.Mul(quoteQuantity)
} else {
feeInQuote += exchangeFee.TakerFeeRate.Mul(quoteQuantity)
}
}
2021-12-04 18:16:48 +00:00
} else if p.FeeRate != nil {
if t.IsMaker {
feeInQuote += p.FeeRate.MakerFeeRate.Mul(quoteQuantity)
} else {
feeInQuote += p.FeeRate.TakerFeeRate.Mul(quoteQuantity)
}
}
2021-01-20 16:49:01 +00:00
}
2021-01-20 08:08:14 +00:00
2021-05-16 09:05:12 +00:00
p.Lock()
defer p.Unlock()
// Base > 0 means we're in long position
// Base < 0 means we're in short position
2021-01-20 08:08:14 +00:00
switch t.Side {
case types.SideTypeBuy:
if p.Base < 0 {
2021-05-22 16:42:57 +00:00
// convert short position to long position
if p.Base+quantity > 0 {
2021-05-22 09:17:37 +00:00
profit = (p.AverageCost - price).Mul(-p.Base)
netProfit = (p.ApproximateAverageCost - price).Mul(-p.Base) - feeInQuote
p.Base += quantity
2021-01-20 16:49:01 +00:00
p.Quote -= quoteQuantity
p.AverageCost = price
p.ApproximateAverageCost = price
2021-05-22 09:17:37 +00:00
return profit, netProfit, true
} else {
// covering short position
p.Base += quantity
2021-01-20 16:49:01 +00:00
p.Quote -= quoteQuantity
2021-05-22 09:17:37 +00:00
profit = (p.AverageCost - price).Mul(quantity)
netProfit = (p.ApproximateAverageCost - price).Mul(quantity) - feeInQuote
2021-05-22 09:17:37 +00:00
return profit, netProfit, true
}
}
2021-01-20 08:08:14 +00:00
p.ApproximateAverageCost = (p.ApproximateAverageCost.Mul(p.Base) + quoteQuantity + feeInQuote).Div(p.Base + quantity)
p.AverageCost = (p.AverageCost.Mul(p.Base) + quoteQuantity).Div(p.Base + quantity)
2021-01-20 08:08:14 +00:00
p.Base += quantity
2021-01-20 16:49:01 +00:00
p.Quote -= quoteQuantity
2021-05-22 09:17:37 +00:00
return 0, 0, false
2021-01-20 08:08:14 +00:00
case types.SideTypeSell:
if p.Base > 0 {
2021-05-22 16:42:57 +00:00
// convert long position to short position
if p.Base-quantity < 0 {
2021-05-22 09:17:37 +00:00
profit = (price - p.AverageCost).Mul(p.Base)
netProfit = (price - p.ApproximateAverageCost).Mul(p.Base) - feeInQuote
p.Base -= quantity
2021-01-20 16:49:01 +00:00
p.Quote += quoteQuantity
p.AverageCost = price
p.ApproximateAverageCost = price
2021-05-22 09:17:37 +00:00
return profit, netProfit, true
} else {
p.Base -= quantity
2021-01-20 16:49:01 +00:00
p.Quote += quoteQuantity
2021-05-22 09:17:37 +00:00
profit = (price - p.AverageCost).Mul(quantity)
netProfit = (price - p.ApproximateAverageCost).Mul(quantity) - feeInQuote
2021-05-22 09:17:37 +00:00
return profit, netProfit, true
}
}
// handling short position, since Base here is negative we need to reverse the sign
p.ApproximateAverageCost = (p.ApproximateAverageCost.Mul(-p.Base) + quoteQuantity - feeInQuote).Div(-p.Base + quantity)
p.AverageCost = (p.AverageCost.Mul(-p.Base) + quoteQuantity).Div(-p.Base + quantity)
2021-01-20 08:08:14 +00:00
p.Base -= quantity
2021-01-20 16:49:01 +00:00
p.Quote += quoteQuantity
2021-01-20 08:08:14 +00:00
2021-05-22 09:17:37 +00:00
return 0, 0, false
2021-01-20 08:08:14 +00:00
}
2021-05-22 09:17:37 +00:00
return 0, 0, false
2021-01-20 08:08:14 +00:00
}