2020-10-26 08:15:30 +00:00
|
|
|
package bbgo
|
|
|
|
|
|
|
|
import (
|
2020-10-27 00:17:42 +00:00
|
|
|
"regexp"
|
|
|
|
|
2020-10-26 08:15:30 +00:00
|
|
|
"github.com/robfig/cron/v3"
|
|
|
|
|
|
|
|
"github.com/c9s/bbgo/pkg/accounting/pnl"
|
2020-10-27 00:17:42 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
"github.com/c9s/bbgo/pkg/util"
|
2020-10-26 08:15:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type PnLReporter interface {
|
|
|
|
Run()
|
|
|
|
}
|
|
|
|
|
|
|
|
type baseReporter struct {
|
|
|
|
notifier Notifier
|
|
|
|
cron *cron.Cron
|
|
|
|
environment *Environment
|
|
|
|
}
|
|
|
|
|
|
|
|
type PnLReporterManager struct {
|
|
|
|
baseReporter
|
|
|
|
|
|
|
|
reporters []PnLReporter
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewPnLReporter(notifier Notifier) *PnLReporterManager {
|
|
|
|
return &PnLReporterManager{
|
|
|
|
baseReporter: baseReporter{
|
|
|
|
notifier: notifier,
|
|
|
|
cron: cron.New(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (manager *PnLReporterManager) AverageCostBySymbols(symbols ...string) *AverageCostPnLReporter {
|
|
|
|
reporter := &AverageCostPnLReporter{
|
|
|
|
baseReporter: manager.baseReporter,
|
|
|
|
Symbols: symbols,
|
|
|
|
}
|
|
|
|
|
|
|
|
manager.reporters = append(manager.reporters, reporter)
|
|
|
|
return reporter
|
|
|
|
}
|
|
|
|
|
|
|
|
type AverageCostPnLReporter struct {
|
|
|
|
baseReporter
|
|
|
|
|
|
|
|
Sessions []string
|
|
|
|
Symbols []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (reporter *AverageCostPnLReporter) Of(sessions ...string) *AverageCostPnLReporter {
|
|
|
|
reporter.Sessions = sessions
|
|
|
|
return reporter
|
|
|
|
}
|
|
|
|
|
|
|
|
func (reporter *AverageCostPnLReporter) When(specs ...string) *AverageCostPnLReporter {
|
|
|
|
for _, spec := range specs {
|
|
|
|
_, err := reporter.cron.AddJob(spec, reporter)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return reporter
|
|
|
|
}
|
|
|
|
|
|
|
|
func (reporter *AverageCostPnLReporter) Run() {
|
|
|
|
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], session.lastPrices[symbol])
|
|
|
|
report.Print()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-27 00:17:42 +00:00
|
|
|
|
2020-10-27 00:48:47 +00:00
|
|
|
type PatternChannelRouter struct {
|
2020-10-27 00:17:42 +00:00
|
|
|
routes map[*regexp.Regexp]string
|
|
|
|
}
|
|
|
|
|
2020-10-27 01:24:59 +00:00
|
|
|
func NewPatternChannelRouter(routes map[string]string) *PatternChannelRouter {
|
|
|
|
router := &PatternChannelRouter{
|
|
|
|
routes: make(map[*regexp.Regexp]string),
|
|
|
|
}
|
|
|
|
if routes != nil {
|
|
|
|
router.AddRoute(routes)
|
|
|
|
}
|
|
|
|
return router
|
|
|
|
}
|
|
|
|
|
2020-10-27 05:54:39 +00:00
|
|
|
func (router *PatternChannelRouter) AddRoute(routes map[string]string) {
|
|
|
|
if routes == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-10-27 00:17:42 +00:00
|
|
|
for pattern, channel := range routes {
|
|
|
|
router.routes[regexp.MustCompile(pattern)] = channel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-27 01:24:59 +00:00
|
|
|
func (router *PatternChannelRouter) Route(text string) (channel string, ok bool) {
|
2020-10-27 00:17:42 +00:00
|
|
|
for pattern, channel := range router.routes {
|
2020-10-27 00:48:47 +00:00
|
|
|
if pattern.MatchString(text) {
|
2020-10-27 00:17:42 +00:00
|
|
|
ok = true
|
|
|
|
return channel, ok
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return channel, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
type ObjectChannelHandler func(obj interface{}) (channel string, ok bool)
|
|
|
|
|
|
|
|
type ObjectChannelRouter struct {
|
|
|
|
routes []ObjectChannelHandler
|
|
|
|
}
|
|
|
|
|
2020-10-27 01:24:59 +00:00
|
|
|
func NewObjectChannelRouter() *ObjectChannelRouter {
|
|
|
|
return &ObjectChannelRouter{}
|
|
|
|
}
|
|
|
|
|
2020-10-27 05:54:39 +00:00
|
|
|
func (router *ObjectChannelRouter) AddRoute(f ObjectChannelHandler) {
|
2020-10-27 00:17:42 +00:00
|
|
|
router.routes = append(router.routes, f)
|
|
|
|
}
|
|
|
|
|
2020-10-27 01:24:59 +00:00
|
|
|
func (router *ObjectChannelRouter) Route(obj interface{}) (channel string, ok bool) {
|
2020-10-27 00:17:42 +00:00
|
|
|
for _, f := range router.routes {
|
|
|
|
channel, ok = f(obj)
|
|
|
|
if ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type TradeReporter struct {
|
|
|
|
notifier Notifier
|
|
|
|
|
|
|
|
channel string
|
|
|
|
channelRoutes map[*regexp.Regexp]string
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewTradeReporter(notifier Notifier) *TradeReporter {
|
|
|
|
return &TradeReporter{
|
|
|
|
notifier: notifier,
|
|
|
|
channelRoutes: make(map[*regexp.Regexp]string),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (reporter *TradeReporter) Channel(channel string) *TradeReporter {
|
|
|
|
reporter.channel = channel
|
|
|
|
return reporter
|
|
|
|
}
|
|
|
|
|
|
|
|
func (reporter *TradeReporter) ChannelBySymbol(routes map[string]string) *TradeReporter {
|
|
|
|
for pattern, channel := range routes {
|
|
|
|
reporter.channelRoutes[regexp.MustCompile(pattern)] = channel
|
|
|
|
}
|
|
|
|
|
|
|
|
return reporter
|
|
|
|
}
|
|
|
|
|
|
|
|
func (reporter *TradeReporter) getChannel(symbol string) string {
|
|
|
|
for pattern, channel := range reporter.channelRoutes {
|
|
|
|
if pattern.MatchString(symbol) {
|
|
|
|
return channel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return reporter.channel
|
|
|
|
}
|
|
|
|
|
|
|
|
func (reporter *TradeReporter) Report(trade types.Trade) {
|
|
|
|
var channel = reporter.getChannel(trade.Symbol)
|
|
|
|
|
|
|
|
var text = util.Render(`:handshake: {{ .Symbol }} {{ .Side }} Trade Execution @ {{ .Price }}`, trade)
|
2020-10-27 12:13:10 +00:00
|
|
|
reporter.notifier.NotifyTo(channel, text, trade)
|
2020-10-27 00:17:42 +00:00
|
|
|
}
|