diff --git a/cmd/pnl.go b/cmd/pnl.go index e2759f4e7..daa7c82e2 100644 --- a/cmd/pnl.go +++ b/cmd/pnl.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/cobra" "github.com/c9s/bbgo/cmd/cmdutil" - "github.com/c9s/bbgo/pkg/accounting" + "github.com/c9s/bbgo/pkg/accounting/pnl" "github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/service" "github.com/c9s/bbgo/pkg/types" @@ -110,7 +110,7 @@ var pnlCmd = &cobra.Command{ currentPrice, err := exchange.QueryAveragePrice(ctx, symbol) - calculator := &accounting.ProfitAndLossCalculator{ + calculator := &pnl.AverageCostCalculator{ TradingFeeCurrency: tradingFeeCurrency, Symbol: symbol, StartTime: startTime, diff --git a/go.mod b/go.mod index c59ba51cf..8c141bacb 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/slack-go/slack v0.6.6-0.20200602212211-b04b8521281b github.com/spf13/cobra v1.0.0 + github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.6.1 github.com/tebeka/strftime v0.1.3 // indirect diff --git a/pkg/accounting/pnl.go b/pkg/accounting/pnl.go index 16b714b18..de5b022b4 100644 --- a/pkg/accounting/pnl.go +++ b/pkg/accounting/pnl.go @@ -1,117 +1,2 @@ package accounting -import ( - "strings" - "time" - - "github.com/sirupsen/logrus" - - "github.com/c9s/bbgo/pkg/types" -) - -type ProfitAndLossCalculator struct { - Symbol string - StartTime time.Time - CurrentPrice float64 - Trades []types.Trade - TradingFeeCurrency string -} - -func (c *ProfitAndLossCalculator) AddTrade(trade types.Trade) { - c.Trades = append(c.Trades, trade) -} - -func (c *ProfitAndLossCalculator) SetCurrentPrice(price float64) { - c.CurrentPrice = price -} - -func (c *ProfitAndLossCalculator) Calculate() *ProfitAndLossReport { - // copy trades, so that we can truncate it. - var trades = c.Trades - var bidVolume = 0.0 - var bidAmount = 0.0 - - var askVolume = 0.0 - - var feeUSD = 0.0 - var bidFeeUSD = 0.0 - var feeRate = 0.0015 - - var currencyFees = map[string]float64{} - - for _, trade := range trades { - if trade.Symbol == c.Symbol { - 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 - } - } - - if _, ok := currencyFees[trade.FeeCurrency]; !ok { - currencyFees[trade.FeeCurrency] = 0.0 - } - currencyFees[trade.FeeCurrency] += trade.Fee - } - - logrus.Infof("average bid price = (total amount %f + total feeUSD %f) / volume %f", bidAmount, bidFeeUSD, bidVolume) - profit := 0.0 - averageCost := (bidAmount + bidFeeUSD) / bidVolume - - for _, t := range trades { - if t.Symbol != c.Symbol { - continue - } - - if t.IsBuyer { - continue - } - - profit += (t.Price - averageCost) * t.Quantity - askVolume += t.Quantity - } - - profit -= feeUSD - unrealizedProfit := profit - - stock := bidVolume - askVolume - if stock > 0 { - stockFee := c.CurrentPrice * stock * feeRate - unrealizedProfit += (c.CurrentPrice-averageCost)*stock - stockFee - } - - return &ProfitAndLossReport{ - Symbol: c.Symbol, - StartTime: c.StartTime, - CurrentPrice: c.CurrentPrice, - NumTrades: len(trades), - - BidVolume: bidVolume, - AskVolume: askVolume, - - Stock: stock, - Profit: profit, - UnrealizedProfit: unrealizedProfit, - AverageBidCost: averageCost, - FeeUSD: feeUSD, - CurrencyFees: currencyFees, - } -} diff --git a/pkg/accounting/pnl/avg_cost.go b/pkg/accounting/pnl/avg_cost.go new file mode 100644 index 000000000..3b47bf09f --- /dev/null +++ b/pkg/accounting/pnl/avg_cost.go @@ -0,0 +1,118 @@ +package pnl + +import ( + "strings" + "time" + + "github.com/sirupsen/logrus" + + "github.com/c9s/bbgo/pkg/accounting" + "github.com/c9s/bbgo/pkg/types" +) + +type AverageCostCalculator struct { + Symbol string + StartTime time.Time + CurrentPrice float64 + Trades []types.Trade + TradingFeeCurrency string +} + +func (c *AverageCostCalculator) AddTrade(trade types.Trade) { + c.Trades = append(c.Trades, trade) +} + +func (c *AverageCostCalculator) SetCurrentPrice(price float64) { + c.CurrentPrice = price +} + +func (c *AverageCostCalculator) Calculate() *accounting.ProfitAndLossReport { + // copy trades, so that we can truncate it. + var trades = c.Trades + var bidVolume = 0.0 + var bidAmount = 0.0 + + var askVolume = 0.0 + + var feeUSD = 0.0 + var bidFeeUSD = 0.0 + var feeRate = 0.0015 + + var currencyFees = map[string]float64{} + + for _, trade := range trades { + if trade.Symbol == c.Symbol { + 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 + } + } + + if _, ok := currencyFees[trade.FeeCurrency]; !ok { + currencyFees[trade.FeeCurrency] = 0.0 + } + currencyFees[trade.FeeCurrency] += trade.Fee + } + + logrus.Infof("average bid price = (total amount %f + total feeUSD %f) / volume %f", bidAmount, bidFeeUSD, bidVolume) + profit := 0.0 + averageCost := (bidAmount + bidFeeUSD) / bidVolume + + for _, t := range trades { + if t.Symbol != c.Symbol { + continue + } + + if t.IsBuyer { + continue + } + + profit += (t.Price - averageCost) * t.Quantity + askVolume += t.Quantity + } + + profit -= feeUSD + unrealizedProfit := profit + + stock := bidVolume - askVolume + if stock > 0 { + stockFee := c.CurrentPrice * stock * feeRate + unrealizedProfit += (c.CurrentPrice-averageCost)*stock - stockFee + } + + return &accounting.ProfitAndLossReport{ + Symbol: c.Symbol, + StartTime: c.StartTime, + CurrentPrice: c.CurrentPrice, + NumTrades: len(trades), + + BidVolume: bidVolume, + AskVolume: askVolume, + + Stock: stock, + Profit: profit, + UnrealizedProfit: unrealizedProfit, + AverageBidCost: averageCost, + FeeUSD: feeUSD, + CurrencyFees: currencyFees, + } +} diff --git a/pkg/bbgo/backtest.go b/pkg/bbgo/backtest.go index 5d6448aeb..60df557fc 100644 --- a/pkg/bbgo/backtest.go +++ b/pkg/bbgo/backtest.go @@ -3,7 +3,7 @@ package bbgo import ( "context" - "github.com/c9s/bbgo/pkg/accounting" + "github.com/c9s/bbgo/pkg/accounting/pnl" "github.com/c9s/bbgo/pkg/types" ) @@ -23,7 +23,7 @@ type BackTestTrader struct { // Context is trading Context Context *Context SourceKLines []types.KLine - ProfitAndLossCalculator *accounting.ProfitAndLossCalculator + ProfitAndLossCalculator *pnl.AverageCostCalculator doneOrders []types.SubmitOrder pendingOrders []types.SubmitOrder @@ -125,7 +125,7 @@ func (trader *BackTestTrader) RunStrategy(ctx context.Context, strategy SingleEx } tradeID++ - trader.ProfitAndLossCalculator.AddTrade(trade) + trader.AverageCostCalculator.AddTrade(trade) trader.doneOrders = append(trader.doneOrders, order) } @@ -135,7 +135,7 @@ func (trader *BackTestTrader) RunStrategy(ctx context.Context, strategy SingleEx } fmt.Print("\n") - report := trader.ProfitAndLossCalculator.Calculate() + report := trader.AverageCostCalculator.Calculate() report.Print() logrus.Infof("wallet balance:") diff --git a/pkg/bbgo/context.go b/pkg/bbgo/context.go index ffd03c18a..d6178743d 100644 --- a/pkg/bbgo/context.go +++ b/pkg/bbgo/context.go @@ -3,7 +3,7 @@ package bbgo import ( "sync" - "github.com/c9s/bbgo/pkg/accounting" + "github.com/c9s/bbgo/pkg/accounting/pnl" "github.com/c9s/bbgo/pkg/types" ) @@ -19,7 +19,7 @@ type Context struct { CurrentPrice float64 Balances map[string]types.Balance - ProfitAndLossCalculator *accounting.ProfitAndLossCalculator + ProfitAndLossCalculator *pnl.AverageCostCalculator StockManager *StockDistribution }