bbgo_origin/pkg/bbgo/profitstats.go

212 lines
5.8 KiB
Go
Raw Normal View History

2021-10-08 06:57:44 +00:00
package bbgo
import (
"fmt"
"github.com/slack-go/slack"
"time"
2021-10-08 06:57:44 +00:00
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
)
2021-10-08 07:09:55 +00:00
// Profit struct stores the PnL information
type Profit struct {
Symbol string `json:"symbol"`
2021-10-08 07:09:55 +00:00
// Profit is the profit of this trade made. negative profit means loss.
Profit fixedpoint.Value `json:"profit" db:"profit"`
// NetProfit is (profit - trading fee)
NetProfit fixedpoint.Value `json:"netProfit" db:"net_profit"`
AverageCost fixedpoint.Value `json:"averageCost" db:"average_ost"`
TradeAmount fixedpoint.Value `json:"tradeAmount" db:"trade_amount"`
2021-10-08 11:41:39 +00:00
// ProfitMargin is a percentage of the profit and the capital amount
ProfitMargin fixedpoint.Value `json:"profitMargin" db:"profit_margin"`
// NetProfitMargin is a percentage of the net profit and the capital amount
NetProfitMargin fixedpoint.Value `json:"netProfitMargin" db:"net_profit_margin"`
2021-10-08 11:41:39 +00:00
QuoteCurrency string `json:"quote_currency" db:"quote_currency"`
2021-10-08 11:43:53 +00:00
BaseCurrency string `json:"base_currency" db:"base_currency"`
2021-10-08 11:41:39 +00:00
2021-10-08 07:09:55 +00:00
// FeeInUSD is the summed fee of this profit,
// you will need to convert the trade fee into USD since the fee currencies can be different.
FeeInUSD fixedpoint.Value `json:"feeInUSD" db:"fee_in_usd"`
Time time.Time `json:"time" db:"time"`
Strategy string `json:"strategy" db:"strategy"`
StrategyInstanceID string `json:"strategyInstanceID" db:"strategy_instance_id"`
}
func (p Profit) SlackAttachment() slack.Attachment {
var title string = fmt.Sprintf("%s PnL ", p.Symbol)
var color string
if p.Profit > 0 {
color = types.GreenColor
title = "+" + p.Profit.String() + " " + p.QuoteCurrency
} else {
color = types.RedColor
title = "-" + p.Profit.String() + " " + p.QuoteCurrency
}
var fields []slack.AttachmentField
if p.NetProfit > 0 {
fields = append(fields, slack.AttachmentField{
Title: "Net Profit",
Value: p.NetProfit.String() + " " + p.QuoteCurrency,
Short: true,
})
}
if p.ProfitMargin > 0 {
fields = append(fields, slack.AttachmentField{
Title: "Profit Margin",
Value: p.ProfitMargin.Percentage(),
Short: true,
})
}
if p.NetProfitMargin > 0 {
fields = append(fields, slack.AttachmentField{
Title: "Net Profit Margin",
Value: p.NetProfitMargin.Percentage(),
Short: true,
})
}
if p.TradeAmount > 0.0 {
fields = append(fields, slack.AttachmentField{
Title: "Trade Amount",
Value: p.TradeAmount.String() + " " + p.QuoteCurrency,
Short: true,
})
}
if p.FeeInUSD > 0 {
fields = append(fields, slack.AttachmentField{
Title: "Fee In USD",
Value: p.FeeInUSD.String() + " USD",
Short: true,
})
}
if len(p.Strategy) > 0 {
fields = append(fields, slack.AttachmentField{
Title: "Strategy",
Value: p.Strategy,
Short: true,
})
}
return slack.Attachment{
Color: color,
Title: title,
Fields: fields,
// Footer: "",
}
}
func (p Profit) PlainText() string {
return fmt.Sprintf("%s trade profit %s %f %s (%.2f%%), net profit =~ %f %s (%.2f%%)",
p.Symbol,
pnlEmoji(p.Profit),
p.Profit.Float64(), p.QuoteCurrency,
p.ProfitMargin.Float64()*100.0,
p.NetProfit.Float64(), p.QuoteCurrency,
p.NetProfitMargin.Float64()*100.0,
)
}
var lossEmoji = "🔥"
var profitEmoji = "💰"
func pnlEmoji(pnl fixedpoint.Value) string {
if pnl < 0 {
return lossEmoji
}
if pnl == 0 {
return ""
}
return profitEmoji
}
2021-10-08 06:57:44 +00:00
type ProfitStats struct {
Symbol string `json:"symbol"`
QuoteCurrency string `json:"quoteCurrency"`
BaseCurrency string `json:"baseCurrency"`
2021-10-08 07:09:55 +00:00
AccumulatedPnL fixedpoint.Value `json:"accumulatedPnL,omitempty"`
AccumulatedNetProfit fixedpoint.Value `json:"accumulatedNetProfit,omitempty"`
AccumulatedProfit fixedpoint.Value `json:"accumulatedProfit,omitempty"`
AccumulatedLoss fixedpoint.Value `json:"accumulatedLoss,omitempty"`
2021-10-08 11:43:53 +00:00
AccumulatedVolume fixedpoint.Value `json:"accumulatedVolume,omitempty"`
2021-10-08 07:09:55 +00:00
AccumulatedSince int64 `json:"accumulatedSince,omitempty"`
TodayPnL fixedpoint.Value `json:"todayPnL,omitempty"`
TodayNetProfit fixedpoint.Value `json:"todayNetProfit,omitempty"`
TodayProfit fixedpoint.Value `json:"todayProfit,omitempty"`
TodayLoss fixedpoint.Value `json:"todayLoss,omitempty"`
TodaySince int64 `json:"todaySince,omitempty"`
2021-10-08 06:57:44 +00:00
}
func (s *ProfitStats) AddProfit(profit Profit) {
s.AccumulatedPnL += profit.Profit
s.AccumulatedNetProfit += profit.NetProfit
s.TodayPnL += profit.Profit
s.TodayNetProfit += profit.NetProfit
2021-10-08 06:57:44 +00:00
if profit.Profit < 0 {
s.AccumulatedLoss += profit.Profit
s.TodayLoss += profit.Profit
} else if profit.Profit > 0 {
s.AccumulatedProfit += profit.Profit
s.TodayProfit += profit.Profit
2021-10-08 06:57:44 +00:00
}
}
func (s *ProfitStats) AddTrade(trade types.Trade) {
if s.IsOver24Hours() {
s.ResetToday()
}
2021-10-08 11:43:53 +00:00
s.AccumulatedVolume += fixedpoint.NewFromFloat(trade.Quantity)
2021-10-08 06:57:44 +00:00
}
func (s *ProfitStats) IsOver24Hours() bool {
return time.Since(time.Unix(s.TodaySince, 0)) > 24*time.Hour
}
func (s *ProfitStats) ResetToday() {
s.TodayPnL = 0
s.TodayNetProfit = 0
s.TodayProfit = 0
s.TodayLoss = 0
var beginningOfTheDay = util.BeginningOfTheDay(time.Now().Local())
s.TodaySince = beginningOfTheDay.Unix()
}
func (s *ProfitStats) PlainText() string {
since := time.Unix(s.AccumulatedSince, 0).Local()
return fmt.Sprintf("today %s profit %f %s,\n"+
"today %s net profit %f %s,\n"+
"today %s trade loss %f %s\n"+
"accumulated profit %f %s,\n"+
"accumulated net profit %f %s,\n"+
"accumulated trade loss %f %s\n"+
"since %s",
s.Symbol, s.TodayPnL.Float64(), s.QuoteCurrency,
s.Symbol, s.TodayNetProfit.Float64(), s.QuoteCurrency,
s.Symbol, s.TodayLoss.Float64(), s.QuoteCurrency,
s.AccumulatedPnL.Float64(), s.QuoteCurrency,
s.AccumulatedNetProfit.Float64(), s.QuoteCurrency,
s.AccumulatedLoss.Float64(), s.QuoteCurrency,
since.Format(time.RFC822),
)
}