mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
use position to calculate the pnl
This commit is contained in:
parent
9d38dc2c87
commit
df683bdf56
|
@ -25,12 +25,14 @@ riskControls:
|
|||
minBaseAssetBalance: 0.0
|
||||
maxOrderAmount: 1000.0
|
||||
|
||||
# example command:
|
||||
# godotenv -f .env.local -- go run ./cmd/bbgo backtest --exchange max --sync-from 2020-11-01 --config config/grid.yaml --base-asset-baseline
|
||||
backtest:
|
||||
# for testing max draw down (MDD) at 03-12
|
||||
# see here for more details
|
||||
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
|
||||
startTime: "2020-10-01"
|
||||
endTime: "2021-10-01"
|
||||
startTime: "2021-01-10"
|
||||
endTime: "2021-01-21"
|
||||
symbols:
|
||||
- BTCUSDT
|
||||
account:
|
||||
|
@ -43,15 +45,15 @@ exchangeStrategies:
|
|||
- on: max
|
||||
grid:
|
||||
symbol: BTCUSDT
|
||||
# quantity: 0.001
|
||||
scaleQuantity:
|
||||
byPrice:
|
||||
exp:
|
||||
domain: [20_000, 30_000]
|
||||
range: [0.2, 0.001]
|
||||
quantity: 0.001
|
||||
# scaleQuantity:
|
||||
# byPrice:
|
||||
# exp:
|
||||
# domain: [20_000, 30_000]
|
||||
# range: [0.2, 0.001]
|
||||
gridNumber: 30
|
||||
profitSpread: 50.0
|
||||
upperPrice: 20_000.0
|
||||
lowerPrice: 30_000.0
|
||||
upperPrice: 50_000.0
|
||||
lowerPrice: 20_000.0
|
||||
long: true
|
||||
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
package pnl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
type AverageCostCalculator struct {
|
||||
TradingFeeCurrency string
|
||||
Market types.Market
|
||||
}
|
||||
|
||||
func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, currentPrice float64) *AverageCostPnlReport {
|
||||
// copy trades, so that we can truncate it.
|
||||
var bidVolume = 0.0
|
||||
var bidAmount = 0.0
|
||||
|
||||
var askVolume = 0.0
|
||||
|
||||
var feeUSD = 0.0
|
||||
var bidFeeUSD = 0.0
|
||||
var feeRate = 0.0015
|
||||
|
||||
if len(trades) == 0 {
|
||||
return &AverageCostPnlReport{
|
||||
|
@ -35,64 +32,41 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
|
|||
|
||||
var currencyFees = map[string]float64{}
|
||||
|
||||
var position = bbgo.NewPositionFromMarket(c.Market)
|
||||
position.SetFeeRate(bbgo.ExchangeFee{
|
||||
// binance vip 0 uses 0.075%
|
||||
MakerFeeRate: fixedpoint.NewFromFloat(0.075 * 0.01),
|
||||
TakerFeeRate: fixedpoint.NewFromFloat(0.075 * 0.01),
|
||||
})
|
||||
|
||||
// TODO: configure the exchange fee rate here later
|
||||
// position.SetExchangeFeeRate()
|
||||
var totalProfit fixedpoint.Value
|
||||
var totalNetProfit fixedpoint.Value
|
||||
|
||||
for _, trade := range trades {
|
||||
if trade.Symbol == symbol {
|
||||
if trade.IsBuyer && trade.Side != types.SideTypeSelf {
|
||||
profit, netProfit, madeProfit := position.AddTrade(trade)
|
||||
if madeProfit {
|
||||
totalProfit += profit
|
||||
totalNetProfit += netProfit
|
||||
}
|
||||
|
||||
if trade.IsBuyer {
|
||||
bidVolume += trade.Quantity
|
||||
bidAmount += trade.Price * trade.Quantity
|
||||
}
|
||||
|
||||
// since we use USDT as the quote currency, we simply check if it matches the currency symbol
|
||||
if strings.HasPrefix(trade.Symbol, trade.FeeCurrency) {
|
||||
bidVolume -= trade.Fee
|
||||
feeUSD += trade.Price * trade.Fee
|
||||
if trade.IsBuyer {
|
||||
bidFeeUSD += trade.Price * trade.Fee
|
||||
}
|
||||
} else if trade.FeeCurrency == "USDT" {
|
||||
feeUSD += trade.Fee
|
||||
if trade.IsBuyer {
|
||||
bidFeeUSD += trade.Fee
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if trade.FeeCurrency == c.TradingFeeCurrency {
|
||||
bidVolume -= trade.Fee
|
||||
} else {
|
||||
askVolume += trade.Quantity
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := currencyFees[trade.FeeCurrency]; !ok {
|
||||
currencyFees[trade.FeeCurrency] = 0.0
|
||||
currencyFees[trade.FeeCurrency] = trade.Fee
|
||||
} else {
|
||||
currencyFees[trade.FeeCurrency] += trade.Fee
|
||||
}
|
||||
currencyFees[trade.FeeCurrency] += trade.Fee
|
||||
}
|
||||
|
||||
profit := 0.0
|
||||
averageCost := (bidAmount + bidFeeUSD) / bidVolume
|
||||
|
||||
for _, t := range trades {
|
||||
if t.Symbol != symbol {
|
||||
continue
|
||||
}
|
||||
|
||||
if t.IsBuyer || t.Side == types.SideTypeSelf {
|
||||
continue
|
||||
}
|
||||
|
||||
profit += (t.Price - averageCost) * t.Quantity
|
||||
askVolume += t.Quantity
|
||||
}
|
||||
|
||||
profit -= feeUSD
|
||||
unrealizedProfit := profit
|
||||
|
||||
stock := bidVolume - askVolume
|
||||
if stock > 0 {
|
||||
stockFee := currentPrice * stock * feeRate
|
||||
unrealizedProfit += (currentPrice-averageCost)*stock - stockFee
|
||||
}
|
||||
|
||||
unrealizedProfit := (fixedpoint.NewFromFloat(currentPrice) - position.AverageCost).Mul(position.Base)
|
||||
return &AverageCostPnlReport{
|
||||
Symbol: symbol,
|
||||
CurrentPrice: currentPrice,
|
||||
|
@ -102,11 +76,12 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
|
|||
BuyVolume: bidVolume,
|
||||
SellVolume: askVolume,
|
||||
|
||||
Stock: stock,
|
||||
Profit: profit,
|
||||
Stock: position.Base.Float64(),
|
||||
Profit: totalProfit,
|
||||
NetProfit: totalNetProfit,
|
||||
UnrealizedProfit: unrealizedProfit,
|
||||
AverageBidCost: averageCost,
|
||||
FeeInUSD: feeUSD,
|
||||
AverageBidCost: position.AverageCost.Float64(),
|
||||
FeeInUSD: (totalProfit - totalNetProfit).Float64(),
|
||||
CurrencyFees: currencyFees,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/slack-go/slack"
|
||||
|
||||
|
@ -18,8 +19,7 @@ type AverageCostPnlReport struct {
|
|||
Market types.Market
|
||||
|
||||
NumTrades int
|
||||
Profit float64
|
||||
UnrealizedProfit float64
|
||||
Profit, NetProfit, UnrealizedProfit fixedpoint.Value
|
||||
AverageBidCost float64
|
||||
BuyVolume float64
|
||||
SellVolume float64
|
||||
|
@ -35,14 +35,16 @@ func (report AverageCostPnlReport) Print() {
|
|||
log.Infof("TOTAL BUY VOLUME: %f", report.BuyVolume)
|
||||
log.Infof("TOTAL SELL VOLUME: %f", report.SellVolume)
|
||||
log.Infof("STOCK: %f", report.Stock)
|
||||
log.Infof("FEE (USD): %f", report.FeeInUSD)
|
||||
|
||||
// FIXME:
|
||||
// log.Infof("FEE (USD): %f", report.FeeInUSD)
|
||||
log.Infof("CURRENT PRICE: %s", types.USD.FormatMoneyFloat64(report.CurrentPrice))
|
||||
log.Infof("CURRENCY FEES:")
|
||||
for currency, fee := range report.CurrencyFees {
|
||||
log.Infof(" - %s: %f", currency, fee)
|
||||
}
|
||||
log.Infof("PROFIT: %s", types.USD.FormatMoneyFloat64(report.Profit))
|
||||
log.Infof("UNREALIZED PROFIT: %s", types.USD.FormatMoneyFloat64(report.UnrealizedProfit))
|
||||
log.Infof("PROFIT: %s", types.USD.FormatMoneyFloat64(report.Profit.Float64()))
|
||||
log.Infof("UNREALIZED PROFIT: %s", types.USD.FormatMoneyFloat64(report.UnrealizedProfit.Float64()))
|
||||
}
|
||||
|
||||
func (report AverageCostPnlReport) SlackAttachment() slack.Attachment {
|
||||
|
@ -63,7 +65,9 @@ func (report AverageCostPnlReport) SlackAttachment() slack.Attachment {
|
|||
{Title: "Unrealized Profit", Value: types.USD.FormatMoney(report.UnrealizedProfit)},
|
||||
{Title: "Current Price", Value: report.Market.FormatPrice(report.CurrentPrice), Short: true},
|
||||
{Title: "Average Cost", Value: report.Market.FormatPrice(report.AverageBidCost), Short: true},
|
||||
{Title: "Fee (USD)", Value: types.USD.FormatMoney(report.FeeInUSD), Short: true},
|
||||
|
||||
// FIXME:
|
||||
// {Title: "Fee (USD)", Value: types.USD.FormatMoney(report.FeeInUSD), Short: true},
|
||||
{Title: "Stock", Value: strconv.FormatFloat(report.Stock, 'f', 8, 64), Short: true},
|
||||
{Title: "Number of Trades", Value: strconv.Itoa(report.NumTrades), Short: true},
|
||||
},
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
"gopkg.in/tucnak/telebot.v2"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/accounting/pnl"
|
||||
"github.com/c9s/bbgo/pkg/cmd/cmdutil"
|
||||
"github.com/c9s/bbgo/pkg/notifier/slacknotifier"
|
||||
"github.com/c9s/bbgo/pkg/notifier/telegramnotifier"
|
||||
|
@ -398,7 +397,9 @@ func (environ *Environment) ConfigureNotificationRouting(conf *NotificationConfi
|
|||
|
||||
}
|
||||
|
||||
// currently not used
|
||||
// currently, not used
|
||||
// FIXME: this is causing cyclic import
|
||||
/*
|
||||
switch conf.Routing.PnL {
|
||||
case "$symbol":
|
||||
environ.ObjectChannelRouter.Route(func(obj interface{}) (channel string, ok bool) {
|
||||
|
@ -410,6 +411,7 @@ func (environ *Environment) ConfigureNotificationRouting(conf *NotificationConfi
|
|||
return
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -31,6 +31,7 @@ type Position struct {
|
|||
// This is used for calculating net profit
|
||||
ApproximateAverageCost fixedpoint.Value `json:"approximateAverageCost"`
|
||||
|
||||
FeeRate *ExchangeFee `json:"feeRate,omitempty"`
|
||||
ExchangeFeeRates map[types.ExchangeName]ExchangeFee `json:"exchangeFeeRates"`
|
||||
|
||||
sync.Mutex
|
||||
|
@ -59,6 +60,10 @@ func (p *Position) Reset() {
|
|||
p.AverageCost = 0
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -168,6 +173,12 @@ func (p *Position) AddTrade(t types.Trade) (profit fixedpoint.Value, netProfit f
|
|||
feeInQuote += exchangeFee.TakerFeeRate.Mul(quoteQuantity)
|
||||
}
|
||||
}
|
||||
} else if p.FeeRate != nil {
|
||||
if t.IsMaker {
|
||||
feeInQuote += p.FeeRate.MakerFeeRate.Mul(quoteQuantity)
|
||||
} else {
|
||||
feeInQuote += p.FeeRate.TakerFeeRate.Mul(quoteQuantity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"regexp"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/accounting/pnl"
|
||||
)
|
||||
|
||||
type PnLReporter interface {
|
||||
|
@ -67,17 +65,20 @@ func (reporter *AverageCostPnLReporter) When(specs ...string) *AverageCostPnLRep
|
|||
}
|
||||
|
||||
func (reporter *AverageCostPnLReporter) Run() {
|
||||
for _, sessionName := range reporter.Sessions {
|
||||
session := reporter.environment.sessions[sessionName]
|
||||
calculator := &pnl.AverageCostCalculator{
|
||||
TradingFeeCurrency: session.Exchange.PlatformFeeCurrency(),
|
||||
}
|
||||
// FIXME: this is causing cyclic import
|
||||
/*
|
||||
for _, sessionName := range reporter.Sessions {
|
||||
session := reporter.environment.sessions[sessionName]
|
||||
calculator := &pnl.AverageCostCalculator{
|
||||
TradingFeeCurrency: session.Exchange.PlatformFeeCurrency(),
|
||||
}
|
||||
|
||||
for _, symbol := range reporter.Symbols {
|
||||
report := calculator.Calculate(symbol, session.Trades[symbol].Copy(), session.lastPrices[symbol])
|
||||
report.Print()
|
||||
for _, symbol := range reporter.Symbols {
|
||||
report := calculator.Calculate(symbol, session.Trades[symbol].Copy(), session.lastPrices[symbol])
|
||||
report.Print()
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
type PatternChannelRouter struct {
|
||||
|
|
|
@ -262,15 +262,17 @@ var BacktestCmd = &cobra.Command{
|
|||
// put the logger back to print the pnl
|
||||
log.SetLevel(log.InfoLevel)
|
||||
for _, session := range environ.Sessions() {
|
||||
calculator := &pnl.AverageCostCalculator{
|
||||
TradingFeeCurrency: backtestExchange.PlatformFeeCurrency(),
|
||||
}
|
||||
for symbol, trades := range session.Trades {
|
||||
market, ok := session.Market(symbol)
|
||||
if !ok {
|
||||
return fmt.Errorf("market not found: %s", symbol)
|
||||
}
|
||||
|
||||
calculator := &pnl.AverageCostCalculator{
|
||||
TradingFeeCurrency: backtestExchange.PlatformFeeCurrency(),
|
||||
Market: market,
|
||||
}
|
||||
|
||||
startPrice, ok := session.StartPrice(symbol)
|
||||
if !ok {
|
||||
return fmt.Errorf("start price not found: %s", symbol)
|
||||
|
|
Loading…
Reference in New Issue
Block a user