qbtrade/pkg/accounting/pnl/avg_cost.go
2024-06-27 22:42:38 +08:00

130 lines
3.0 KiB
Go

package pnl
import (
"time"
log "github.com/sirupsen/logrus"
"git.qtrade.icu/lychiyu/qbtrade/pkg/fixedpoint"
"git.qtrade.icu/lychiyu/qbtrade/pkg/types"
)
type AverageCostCalculator struct {
TradingFeeCurrency string
Market types.Market
ExchangeFee *types.ExchangeFee
}
func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, currentPrice fixedpoint.Value) *AverageCostPnLReport {
// copy trades, so that we can truncate it.
var bidVolume = fixedpoint.Zero
var askVolume = fixedpoint.Zero
var feeUSD = fixedpoint.Zero
var grossProfit = fixedpoint.Zero
var grossLoss = fixedpoint.Zero
var position = types.NewPositionFromMarket(c.Market)
if c.ExchangeFee != nil {
position.SetFeeRate(*c.ExchangeFee)
} else {
makerFeeRate := 0.075 * 0.01
if c.Market.QuoteCurrency == "BUSD" {
makerFeeRate = 0
}
position.SetFeeRate(types.ExchangeFee{
// binance vip 0 uses 0.075%
MakerFeeRate: fixedpoint.NewFromFloat(makerFeeRate),
TakerFeeRate: fixedpoint.NewFromFloat(0.075 * 0.01),
})
}
if len(trades) == 0 {
return &AverageCostPnLReport{
Symbol: symbol,
Market: c.Market,
LastPrice: currentPrice,
NumTrades: 0,
Position: position,
BuyVolume: bidVolume,
SellVolume: askVolume,
FeeInUSD: feeUSD,
}
}
var currencyFees = map[string]fixedpoint.Value{}
// TODO: configure the exchange fee rate here later
// position.SetExchangeFeeRate()
var totalProfit fixedpoint.Value
var totalNetProfit fixedpoint.Value
var tradeIDs = map[uint64]types.Trade{}
for _, trade := range trades {
if _, exists := tradeIDs[trade.ID]; exists {
log.Warnf("duplicated trade: %+v", trade)
continue
}
if trade.Symbol != symbol {
continue
}
profit, netProfit, madeProfit := position.AddTrade(trade)
if madeProfit {
totalProfit = totalProfit.Add(profit)
totalNetProfit = totalNetProfit.Add(netProfit)
}
if profit.Sign() > 0 {
grossProfit = grossProfit.Add(profit)
} else if profit.Sign() < 0 {
grossLoss = grossLoss.Add(profit)
}
if trade.IsBuyer {
bidVolume = bidVolume.Add(trade.Quantity)
} else {
askVolume = askVolume.Add(trade.Quantity)
}
if _, ok := currencyFees[trade.FeeCurrency]; !ok {
currencyFees[trade.FeeCurrency] = trade.Fee
} else {
currencyFees[trade.FeeCurrency] = currencyFees[trade.FeeCurrency].Add(trade.Fee)
}
tradeIDs[trade.ID] = trade
}
unrealizedProfit := currentPrice.Sub(position.AverageCost).
Mul(position.GetBase())
return &AverageCostPnLReport{
Symbol: symbol,
Market: c.Market,
LastPrice: currentPrice,
NumTrades: len(trades),
StartTime: time.Time(trades[0].Time),
Position: position,
BuyVolume: bidVolume,
SellVolume: askVolume,
BaseAssetPosition: position.GetBase(),
Profit: totalProfit,
NetProfit: totalNetProfit,
UnrealizedProfit: unrealizedProfit,
GrossProfit: grossProfit,
GrossLoss: grossLoss,
AverageCost: position.AverageCost,
FeeInUSD: totalProfit.Sub(totalNetProfit),
CurrencyFees: currencyFees,
}
}