2023-06-16 06:56:22 +00:00
|
|
|
package report
|
|
|
|
|
|
|
|
import (
|
2023-07-10 07:20:00 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/bbgo"
|
|
|
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
2023-06-16 06:56:22 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ProfitTracker struct {
|
|
|
|
types.IntervalWindow
|
|
|
|
|
2023-06-16 10:06:47 +00:00
|
|
|
// Accumulated profit report
|
|
|
|
AccumulatedProfitReport *AccumulatedProfitReport `json:"accumulatedProfitReport"`
|
|
|
|
|
|
|
|
Market types.Market
|
|
|
|
|
2023-06-16 07:18:15 +00:00
|
|
|
ProfitStatsSlice []*types.ProfitStats
|
|
|
|
CurrentProfitStats **types.ProfitStats
|
|
|
|
|
2023-06-16 10:06:47 +00:00
|
|
|
tradeStats *types.TradeStats
|
2023-06-16 06:56:22 +00:00
|
|
|
}
|
|
|
|
|
2023-06-16 10:06:47 +00:00
|
|
|
// InitOld is for backward capability. ps is the ProfitStats of the strategy, Market is the strategy Market
|
|
|
|
func (p *ProfitTracker) InitOld(market types.Market, ps **types.ProfitStats, ts *types.TradeStats) {
|
|
|
|
p.Market = market
|
2023-06-16 06:56:22 +00:00
|
|
|
|
|
|
|
if *ps == nil {
|
2023-06-16 10:06:47 +00:00
|
|
|
*ps = types.NewProfitStats(p.Market)
|
2023-06-16 06:56:22 +00:00
|
|
|
}
|
|
|
|
|
2023-06-16 10:06:47 +00:00
|
|
|
p.tradeStats = ts
|
|
|
|
|
2023-06-16 07:18:15 +00:00
|
|
|
p.CurrentProfitStats = ps
|
|
|
|
p.ProfitStatsSlice = append(p.ProfitStatsSlice, *ps)
|
2023-06-16 06:56:22 +00:00
|
|
|
|
2023-06-16 10:06:47 +00:00
|
|
|
if p.AccumulatedProfitReport != nil {
|
|
|
|
p.AccumulatedProfitReport.Initialize(p.Market.Symbol, p.Interval, p.Window)
|
|
|
|
}
|
2023-06-16 06:56:22 +00:00
|
|
|
}
|
|
|
|
|
2023-06-16 10:06:47 +00:00
|
|
|
// Init initialize the tracker with the given Market
|
|
|
|
func (p *ProfitTracker) Init(market types.Market, ts *types.TradeStats) {
|
|
|
|
ps := types.NewProfitStats(p.Market)
|
|
|
|
p.InitOld(market, &ps, ts)
|
2023-06-16 06:56:22 +00:00
|
|
|
}
|
|
|
|
|
2023-07-10 07:20:00 +00:00
|
|
|
func (p *ProfitTracker) Bind(session *bbgo.ExchangeSession, tradeCollector *bbgo.TradeCollector) {
|
|
|
|
session.Subscribe(types.KLineChannel, p.Market.Symbol, types.SubscribeOptions{Interval: p.Interval})
|
|
|
|
|
|
|
|
tradeCollector.OnProfit(func(trade types.Trade, profit *types.Profit) {
|
|
|
|
if profit == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
p.AddProfit(*profit)
|
|
|
|
})
|
|
|
|
|
|
|
|
tradeCollector.OnTrade(func(trade types.Trade, profit fixedpoint.Value, netProfit fixedpoint.Value) {
|
|
|
|
p.AddTrade(trade)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Rotate profitStats slice
|
|
|
|
session.MarketDataStream.OnKLineClosed(types.KLineWith(p.Market.Symbol, p.Interval, func(kline types.KLine) {
|
|
|
|
p.Rotate()
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2023-06-16 06:56:22 +00:00
|
|
|
// Rotate the tracker to make a new ProfitStats to record the profits
|
|
|
|
func (p *ProfitTracker) Rotate() {
|
2023-06-16 10:32:44 +00:00
|
|
|
// Update report
|
|
|
|
if p.AccumulatedProfitReport != nil {
|
|
|
|
p.AccumulatedProfitReport.Rotate(*p.CurrentProfitStats, p.tradeStats)
|
|
|
|
}
|
|
|
|
|
2023-06-16 10:06:47 +00:00
|
|
|
*p.CurrentProfitStats = types.NewProfitStats(p.Market)
|
2023-06-16 07:18:15 +00:00
|
|
|
p.ProfitStatsSlice = append(p.ProfitStatsSlice, *p.CurrentProfitStats)
|
2023-06-16 06:56:22 +00:00
|
|
|
// Truncate
|
2023-06-16 07:18:15 +00:00
|
|
|
if len(p.ProfitStatsSlice) > p.Window {
|
|
|
|
p.ProfitStatsSlice = p.ProfitStatsSlice[len(p.ProfitStatsSlice)-p.Window:]
|
2023-06-16 06:56:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *ProfitTracker) AddProfit(profit types.Profit) {
|
2023-06-16 07:18:15 +00:00
|
|
|
(*p.CurrentProfitStats).AddProfit(profit)
|
2023-06-16 06:56:22 +00:00
|
|
|
}
|
2023-06-16 07:06:58 +00:00
|
|
|
|
|
|
|
func (p *ProfitTracker) AddTrade(trade types.Trade) {
|
2023-06-16 07:18:15 +00:00
|
|
|
(*p.CurrentProfitStats).AddTrade(trade)
|
2023-06-16 10:06:47 +00:00
|
|
|
|
|
|
|
if p.AccumulatedProfitReport != nil {
|
|
|
|
p.AccumulatedProfitReport.AddTrade(trade)
|
|
|
|
}
|
2023-06-16 07:06:58 +00:00
|
|
|
}
|