mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
all: resolve import cycle
This commit is contained in:
parent
0947c28294
commit
c86b29e6dc
|
@ -30,6 +30,7 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/slack/slacklog"
|
"github.com/c9s/bbgo/pkg/slack/slacklog"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
|
"github.com/c9s/bbgo/pkg/util/templateutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -366,7 +367,7 @@ func (environ *Environment) ConfigureNotificationRouting(conf *NotificationConfi
|
||||||
|
|
||||||
case "$session":
|
case "$session":
|
||||||
defaultOrderUpdateHandler := func(order types.Order) {
|
defaultOrderUpdateHandler := func(order types.Order) {
|
||||||
text := util.Render(TemplateOrderReport, order)
|
text := templateutil.Render(TemplateOrderReport, order)
|
||||||
Notify(text, &order)
|
Notify(text, &order)
|
||||||
}
|
}
|
||||||
for name := range environ.sessions {
|
for name := range environ.sessions {
|
||||||
|
@ -376,7 +377,7 @@ func (environ *Environment) ConfigureNotificationRouting(conf *NotificationConfi
|
||||||
channel, ok := Notification.SessionChannelRouter.Route(name)
|
channel, ok := Notification.SessionChannelRouter.Route(name)
|
||||||
if ok {
|
if ok {
|
||||||
session.UserDataStream.OnOrderUpdate(func(order types.Order) {
|
session.UserDataStream.OnOrderUpdate(func(order types.Order) {
|
||||||
text := util.Render(TemplateOrderReport, order)
|
text := templateutil.Render(TemplateOrderReport, order)
|
||||||
NotifyTo(channel, text, &order)
|
NotifyTo(channel, text, &order)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -397,7 +398,7 @@ func (environ *Environment) ConfigureNotificationRouting(conf *NotificationConfi
|
||||||
|
|
||||||
// use same handler for each session
|
// use same handler for each session
|
||||||
handler := func(order types.Order) {
|
handler := func(order types.Order) {
|
||||||
text := util.Render(TemplateOrderReport, order)
|
text := templateutil.Render(TemplateOrderReport, order)
|
||||||
channel, ok := Notification.RouteObject(&order)
|
channel, ok := Notification.RouteObject(&order)
|
||||||
if ok {
|
if ok {
|
||||||
NotifyTo(channel, text, &order)
|
NotifyTo(channel, text, &order)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/dynamic"
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/interact"
|
"github.com/c9s/bbgo/pkg/interact"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
@ -526,7 +527,7 @@ func (it *CoreInteraction) Initialize() error {
|
||||||
// getStrategySignature returns strategy instance unique signature
|
// getStrategySignature returns strategy instance unique signature
|
||||||
func getStrategySignature(strategy SingleExchangeStrategy) (string, error) {
|
func getStrategySignature(strategy SingleExchangeStrategy) (string, error) {
|
||||||
// Returns instance ID
|
// Returns instance ID
|
||||||
var signature = CallID(strategy)
|
var signature = dynamic.CallID(strategy)
|
||||||
if signature != "" {
|
if signature != "" {
|
||||||
return signature, nil
|
return signature, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ func (p *Persistence) Save(val interface{}, subIDs ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Persistence) Sync(obj interface{}) error {
|
func (p *Persistence) Sync(obj interface{}) error {
|
||||||
id := CallID(obj)
|
id := dynamic.CallID(obj)
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ func (p *Persistence) Sync(obj interface{}) error {
|
||||||
|
|
||||||
// Sync syncs the object properties into the persistence layer
|
// Sync syncs the object properties into the persistence layer
|
||||||
func Sync(obj interface{}) {
|
func Sync(obj interface{}) {
|
||||||
id := CallID(obj)
|
id := dynamic.CallID(obj)
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
log.Warnf("InstanceID() is not provided, can not sync persistence")
|
log.Warnf("InstanceID() is not provided, can not sync persistence")
|
||||||
return
|
return
|
||||||
|
|
|
@ -57,13 +57,13 @@ func preparePersistentServices() []service.PersistenceService {
|
||||||
|
|
||||||
func Test_CallID(t *testing.T) {
|
func Test_CallID(t *testing.T) {
|
||||||
t.Run("default", func(t *testing.T) {
|
t.Run("default", func(t *testing.T) {
|
||||||
id := CallID(&TestStruct{})
|
id := dynamic.CallID(&TestStruct{})
|
||||||
assert.NotEmpty(t, id)
|
assert.NotEmpty(t, id)
|
||||||
assert.Equal(t, "test-struct", id)
|
assert.Equal(t, "test-struct", id)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fallback", func(t *testing.T) {
|
t.Run("fallback", func(t *testing.T) {
|
||||||
id := CallID(&TestStructWithoutInstanceID{Symbol: "BTCUSDT"})
|
id := dynamic.CallID(&TestStructWithoutInstanceID{Symbol: "BTCUSDT"})
|
||||||
assert.Equal(t, "test-struct-no-instance-id:BTCUSDT", id)
|
assert.Equal(t, "test-struct-no-instance-id:BTCUSDT", id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ func Test_storePersistenceFields(t *testing.T) {
|
||||||
for _, ps := range pss {
|
for _, ps := range pss {
|
||||||
psName := reflect.TypeOf(ps).Elem().String()
|
psName := reflect.TypeOf(ps).Elem().String()
|
||||||
t.Run("all/"+psName, func(t *testing.T) {
|
t.Run("all/"+psName, func(t *testing.T) {
|
||||||
id := CallID(a)
|
id := dynamic.CallID(a)
|
||||||
err := storePersistenceFields(a, id, ps)
|
err := storePersistenceFields(a, id, ps)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,2 @@
|
||||||
package bbgo
|
package bbgo
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/dynamic"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InstanceIDProvider interface {
|
|
||||||
InstanceID() string
|
|
||||||
}
|
|
||||||
|
|
||||||
func CallID(obj interface{}) string {
|
|
||||||
sv := reflect.ValueOf(obj)
|
|
||||||
st := reflect.TypeOf(obj)
|
|
||||||
if st.Implements(reflect.TypeOf((*InstanceIDProvider)(nil)).Elem()) {
|
|
||||||
m := sv.MethodByName("InstanceID")
|
|
||||||
ret := m.Call(nil)
|
|
||||||
return ret[0].String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if symbol, ok := dynamic.LookupSymbolField(sv); ok {
|
|
||||||
m := sv.MethodByName("ID")
|
|
||||||
ret := m.Call(nil)
|
|
||||||
return ret[0].String() + ":" + symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallback to just ID
|
|
||||||
m := sv.MethodByName("ID")
|
|
||||||
ret := m.Call(nil)
|
|
||||||
return ret[0].String() + ":"
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/cache"
|
"github.com/c9s/bbgo/pkg/cache"
|
||||||
|
"github.com/c9s/bbgo/pkg/util/templateutil"
|
||||||
|
|
||||||
exchange2 "github.com/c9s/bbgo/pkg/exchange"
|
exchange2 "github.com/c9s/bbgo/pkg/exchange"
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
@ -848,7 +849,7 @@ func (session *ExchangeSession) SlackAttachment() slack.Attachment {
|
||||||
Title: session.Name,
|
Title: session.Name,
|
||||||
Fields: fields,
|
Fields: fields,
|
||||||
FooterIcon: footerIcon,
|
FooterIcon: footerIcon,
|
||||||
Footer: util.Render("update time {{ . }}", time.Now().Format(time.RFC822)),
|
Footer: templateutil.Render("update time {{ . }}", time.Now().Format(time.RFC822)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -384,7 +384,7 @@ func (trader *Trader) LoadState() error {
|
||||||
log.Infof("loading strategies states...")
|
log.Infof("loading strategies states...")
|
||||||
|
|
||||||
return trader.IterateStrategies(func(strategy StrategyID) error {
|
return trader.IterateStrategies(func(strategy StrategyID) error {
|
||||||
id := CallID(strategy)
|
id := dynamic.CallID(strategy)
|
||||||
return loadPersistenceFields(strategy, id, ps)
|
return loadPersistenceFields(strategy, id, ps)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -420,7 +420,7 @@ func (trader *Trader) SaveState() error {
|
||||||
|
|
||||||
log.Infof("saving strategies states...")
|
log.Infof("saving strategies states...")
|
||||||
return trader.IterateStrategies(func(strategy StrategyID) error {
|
return trader.IterateStrategies(func(strategy StrategyID) error {
|
||||||
id := CallID(strategy)
|
id := dynamic.CallID(strategy)
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
30
pkg/dynamic/id.go
Normal file
30
pkg/dynamic/id.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package dynamic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InstanceIDProvider interface {
|
||||||
|
InstanceID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallID(obj interface{}) string {
|
||||||
|
sv := reflect.ValueOf(obj)
|
||||||
|
st := reflect.TypeOf(obj)
|
||||||
|
if st.Implements(reflect.TypeOf((*InstanceIDProvider)(nil)).Elem()) {
|
||||||
|
m := sv.MethodByName("InstanceID")
|
||||||
|
ret := m.Call(nil)
|
||||||
|
return ret[0].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if symbol, ok := LookupSymbolField(sv); ok {
|
||||||
|
m := sv.MethodByName("ID")
|
||||||
|
ret := m.Call(nil)
|
||||||
|
return ret[0].String() + ":" + symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to just ID
|
||||||
|
m := sv.MethodByName("ID")
|
||||||
|
ret := m.Call(nil)
|
||||||
|
return ret[0].String() + ":"
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package util
|
package dynamic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -11,25 +11,10 @@ import (
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
"github.com/jedib0t/go-pretty/v6/text"
|
"github.com/jedib0t/go-pretty/v6/text"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/bbgo"
|
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDefaultTableStyle() *table.Style {
|
|
||||||
style := table.Style{
|
|
||||||
Name: "StyleRounded",
|
|
||||||
Box: table.StyleBoxRounded,
|
|
||||||
Format: table.FormatOptionsDefault,
|
|
||||||
HTML: table.DefaultHTMLOptions,
|
|
||||||
Options: table.OptionsDefault,
|
|
||||||
Title: table.TitleOptionsDefault,
|
|
||||||
Color: table.ColorOptionsYellowWhiteOnBlack,
|
|
||||||
}
|
|
||||||
style.Color.Row = text.Colors{text.FgHiYellow, text.BgHiBlack}
|
|
||||||
style.Color.RowAlternate = text.Colors{text.FgYellow, text.BgBlack}
|
|
||||||
return &style
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultWhiteList() []string {
|
func DefaultWhiteList() []string {
|
||||||
return []string{"Window", "Interval", "Symbol"}
|
return []string{"Window", "Interval", "Symbol"}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +43,7 @@ func PrintConfig(s interface{}, f io.Writer, style *table.Style, withColor bool,
|
||||||
})
|
})
|
||||||
t.AppendHeader(table.Row{"json", "struct field name", "type", "value"})
|
t.AppendHeader(table.Row{"json", "struct field name", "type", "value"})
|
||||||
}
|
}
|
||||||
write(f, "---- %s Settings ---\n", bbgo.CallID(s))
|
write(f, "---- %s Settings ---\n", CallID(s))
|
||||||
|
|
||||||
embeddedWhiteSet := map[string]struct{}{}
|
embeddedWhiteSet := map[string]struct{}{}
|
||||||
for _, whiteList := range whiteLists {
|
for _, whiteList := range whiteLists {
|
||||||
|
@ -71,7 +56,7 @@ func PrintConfig(s interface{}, f io.Writer, style *table.Style, withColor bool,
|
||||||
|
|
||||||
val := reflect.ValueOf(s)
|
val := reflect.ValueOf(s)
|
||||||
|
|
||||||
if val.Type().Kind() == Pointer {
|
if val.Type().Kind() == util.Pointer {
|
||||||
val = val.Elem()
|
val = val.Elem()
|
||||||
}
|
}
|
||||||
var values types.JsonArr
|
var values types.JsonArr
|
||||||
|
@ -88,7 +73,7 @@ func PrintConfig(s interface{}, f io.Writer, style *table.Style, withColor bool,
|
||||||
if t.Anonymous {
|
if t.Anonymous {
|
||||||
var target reflect.Type
|
var target reflect.Type
|
||||||
var field reflect.Value
|
var field reflect.Value
|
||||||
if t.Type.Kind() == Pointer {
|
if t.Type.Kind() == util.Pointer {
|
||||||
target = t.Type.Elem()
|
target = t.Type.Elem()
|
||||||
field = val.Field(i).Elem()
|
field = val.Field(i).Elem()
|
||||||
} else {
|
} else {
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/dynamic"
|
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
|
||||||
|
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/dynamic"
|
||||||
|
style2 "github.com/c9s/bbgo/pkg/style"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Strategy) ParamDump(f io.Writer, seriesLength ...int) {
|
func (s *Strategy) ParamDump(f io.Writer, seriesLength ...int) {
|
||||||
|
@ -95,7 +95,7 @@ func (s *Strategy) ParamDump(f io.Writer, seriesLength ...int) {
|
||||||
func (s *Strategy) Print(f io.Writer, pretty bool, withColor ...bool) {
|
func (s *Strategy) Print(f io.Writer, pretty bool, withColor ...bool) {
|
||||||
var style *table.Style
|
var style *table.Style
|
||||||
if pretty {
|
if pretty {
|
||||||
style = util.NewDefaultTableStyle()
|
style = style2.NewDefaultTableStyle()
|
||||||
}
|
}
|
||||||
util.PrintConfig(s, f, style, len(withColor) > 0 && withColor[0], util.DefaultWhiteList()...)
|
dynamic.PrintConfig(s, f, style, len(withColor) > 0 && withColor[0], dynamic.DefaultWhiteList()...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
|
"github.com/c9s/bbgo/pkg/util/templateutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ID = "xbalance"
|
const ID = "xbalance"
|
||||||
|
@ -39,7 +40,7 @@ func (s *State) IsOver24Hours() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) PlainText() string {
|
func (s *State) PlainText() string {
|
||||||
return util.Render(`{{ .Asset }} transfer stats:
|
return templateutil.Render(`{{ .Asset }} transfer stats:
|
||||||
daily number of transfers: {{ .DailyNumberOfTransfers }}
|
daily number of transfers: {{ .DailyNumberOfTransfers }}
|
||||||
daily amount of transfers {{ .DailyAmountOfTransfers.Float64 }}`, s)
|
daily amount of transfers {{ .DailyAmountOfTransfers.Float64 }}`, s)
|
||||||
}
|
}
|
||||||
|
@ -53,12 +54,12 @@ func (s *State) SlackAttachment() slack.Attachment {
|
||||||
{Title: "Total Number of Transfers", Value: fmt.Sprintf("%d", s.DailyNumberOfTransfers), Short: true},
|
{Title: "Total Number of Transfers", Value: fmt.Sprintf("%d", s.DailyNumberOfTransfers), Short: true},
|
||||||
{Title: "Total Amount of Transfers", Value: util.FormatFloat(s.DailyAmountOfTransfers.Float64(), 4), Short: true},
|
{Title: "Total Amount of Transfers", Value: util.FormatFloat(s.DailyAmountOfTransfers.Float64(), 4), Short: true},
|
||||||
},
|
},
|
||||||
Footer: util.Render("Since {{ . }}", time.Unix(s.Since, 0).Format(time.RFC822)),
|
Footer: templateutil.Render("Since {{ . }}", time.Unix(s.Since, 0).Format(time.RFC822)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Reset() {
|
func (s *State) Reset() {
|
||||||
var beginningOfTheDay = util.BeginningOfTheDay(time.Now().Local())
|
var beginningOfTheDay = types.BeginningOfTheDay(time.Now().Local())
|
||||||
*s = State{
|
*s = State{
|
||||||
DailyNumberOfTransfers: 0,
|
DailyNumberOfTransfers: 0,
|
||||||
DailyAmountOfTransfers: fixedpoint.Zero,
|
DailyAmountOfTransfers: fixedpoint.Zero,
|
||||||
|
@ -93,7 +94,7 @@ func (r *WithdrawalRequest) PlainText() string {
|
||||||
|
|
||||||
func (r *WithdrawalRequest) SlackAttachment() slack.Attachment {
|
func (r *WithdrawalRequest) SlackAttachment() slack.Attachment {
|
||||||
var color = "#DC143C"
|
var color = "#DC143C"
|
||||||
title := util.Render(`Withdraw Request {{ .Asset }}`, r)
|
title := templateutil.Render(`Withdraw Request {{ .Asset }}`, r)
|
||||||
return slack.Attachment{
|
return slack.Attachment{
|
||||||
// Pretext: "",
|
// Pretext: "",
|
||||||
// Text: text,
|
// Text: text,
|
||||||
|
@ -105,7 +106,7 @@ func (r *WithdrawalRequest) SlackAttachment() slack.Attachment {
|
||||||
{Title: "From", Value: r.FromSession},
|
{Title: "From", Value: r.FromSession},
|
||||||
{Title: "To", Value: r.ToSession},
|
{Title: "To", Value: r.ToSession},
|
||||||
},
|
},
|
||||||
Footer: util.Render("Time {{ . }}", time.Now().Format(time.RFC822)),
|
Footer: templateutil.Render("Time {{ . }}", time.Now().Format(time.RFC822)),
|
||||||
// FooterIcon: "",
|
// FooterIcon: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/util/templateutil"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -32,11 +33,11 @@ type State struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) IsOver24Hours() bool {
|
func (s *State) IsOver24Hours() bool {
|
||||||
return util.Over24Hours(time.Unix(s.Since, 0))
|
return types.Over24Hours(time.Unix(s.Since, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) PlainText() string {
|
func (s *State) PlainText() string {
|
||||||
return util.Render(`{{ .Asset }} transfer stats:
|
return templateutil.Render(`{{ .Asset }} transfer stats:
|
||||||
daily number of transfers: {{ .DailyNumberOfTransfers }}
|
daily number of transfers: {{ .DailyNumberOfTransfers }}
|
||||||
daily amount of transfers {{ .DailyAmountOfTransfers.Float64 }}`, s)
|
daily amount of transfers {{ .DailyAmountOfTransfers.Float64 }}`, s)
|
||||||
}
|
}
|
||||||
|
@ -46,12 +47,12 @@ func (s *State) SlackAttachment() slack.Attachment {
|
||||||
// Pretext: "",
|
// Pretext: "",
|
||||||
// Text: text,
|
// Text: text,
|
||||||
Fields: []slack.AttachmentField{},
|
Fields: []slack.AttachmentField{},
|
||||||
Footer: util.Render("Since {{ . }}", time.Unix(s.Since, 0).Format(time.RFC822)),
|
Footer: templateutil.Render("Since {{ . }}", time.Unix(s.Since, 0).Format(time.RFC822)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Reset() {
|
func (s *State) Reset() {
|
||||||
var beginningOfTheDay = util.BeginningOfTheDay(time.Now().Local())
|
var beginningOfTheDay = types.BeginningOfTheDay(time.Now().Local())
|
||||||
*s = State{
|
*s = State{
|
||||||
Since: beginningOfTheDay.Unix(),
|
Since: beginningOfTheDay.Unix(),
|
||||||
}
|
}
|
||||||
|
|
5
pkg/style/colors.go
Normal file
5
pkg/style/colors.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package style
|
||||||
|
|
||||||
|
const GreenColor = "#228B22"
|
||||||
|
const RedColor = "#800000"
|
||||||
|
const GrayColor = "#f0f0f0"
|
61
pkg/style/pnl.go
Normal file
61
pkg/style/pnl.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package style
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
)
|
||||||
|
|
||||||
|
var LossEmoji = "🔥"
|
||||||
|
var ProfitEmoji = "💰"
|
||||||
|
var DefaultPnLLevelResolution = fixedpoint.NewFromFloat(0.001)
|
||||||
|
|
||||||
|
func PnLColor(pnl fixedpoint.Value) string {
|
||||||
|
if pnl.Sign() > 0 {
|
||||||
|
return GreenColor
|
||||||
|
}
|
||||||
|
return RedColor
|
||||||
|
}
|
||||||
|
|
||||||
|
func PnLSignString(pnl fixedpoint.Value) string {
|
||||||
|
if pnl.Sign() > 0 {
|
||||||
|
return "+" + pnl.String()
|
||||||
|
}
|
||||||
|
return pnl.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func PnLEmojiSimple(pnl fixedpoint.Value) string {
|
||||||
|
if pnl.Sign() < 0 {
|
||||||
|
return LossEmoji
|
||||||
|
}
|
||||||
|
|
||||||
|
if pnl.IsZero() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProfitEmoji
|
||||||
|
}
|
||||||
|
|
||||||
|
func PnLEmojiMargin(pnl, margin, resolution fixedpoint.Value) (out string) {
|
||||||
|
if margin.IsZero() {
|
||||||
|
return PnLEmojiSimple(pnl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pnl.Sign() < 0 {
|
||||||
|
out = LossEmoji
|
||||||
|
level := (margin.Neg()).Div(resolution).Int()
|
||||||
|
for i := 1; i < level; i++ {
|
||||||
|
out += LossEmoji
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
if pnl.IsZero() {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
out = ProfitEmoji
|
||||||
|
level := margin.Div(resolution).Int()
|
||||||
|
for i := 1; i < level; i++ {
|
||||||
|
out += ProfitEmoji
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
21
pkg/style/table.go
Normal file
21
pkg/style/table.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package style
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
|
"github.com/jedib0t/go-pretty/v6/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDefaultTableStyle() *table.Style {
|
||||||
|
style := table.Style{
|
||||||
|
Name: "StyleRounded",
|
||||||
|
Box: table.StyleBoxRounded,
|
||||||
|
Format: table.FormatOptionsDefault,
|
||||||
|
HTML: table.DefaultHTMLOptions,
|
||||||
|
Options: table.OptionsDefault,
|
||||||
|
Title: table.TitleOptionsDefault,
|
||||||
|
Color: table.ColorOptionsYellowWhiteOnBlack,
|
||||||
|
}
|
||||||
|
style.Color.Row = text.Colors{text.FgHiYellow, text.BgHiBlack}
|
||||||
|
style.Color.RowAlternate = text.Colors{text.FgYellow, text.BgBlack}
|
||||||
|
return &style
|
||||||
|
}
|
2
pkg/types/colors.go
Normal file
2
pkg/types/colors.go
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
package types
|
||||||
|
|
5
pkg/types/instance.go
Normal file
5
pkg/types/instance.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type InstanceIDProvider interface {
|
||||||
|
InstanceID() string
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/style"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Direction int
|
type Direction int
|
||||||
|
@ -214,11 +214,11 @@ func (k KLine) GetChange() fixedpoint.Value {
|
||||||
|
|
||||||
func (k KLine) Color() string {
|
func (k KLine) Color() string {
|
||||||
if k.Direction() > 0 {
|
if k.Direction() > 0 {
|
||||||
return util.GreenColor
|
return style.GreenColor
|
||||||
} else if k.Direction() < 0 {
|
} else if k.Direction() < 0 {
|
||||||
return util.RedColor
|
return style.RedColor
|
||||||
}
|
}
|
||||||
return util.GrayColor
|
return style.GrayColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k KLine) String() string {
|
func (k KLine) String() string {
|
||||||
|
@ -237,29 +237,29 @@ func (k KLine) SlackAttachment() slack.Attachment {
|
||||||
Text: fmt.Sprintf("*%s* KLine %s", k.Symbol, k.Interval),
|
Text: fmt.Sprintf("*%s* KLine %s", k.Symbol, k.Interval),
|
||||||
Color: k.Color(),
|
Color: k.Color(),
|
||||||
Fields: []slack.AttachmentField{
|
Fields: []slack.AttachmentField{
|
||||||
{Title: "Open", Value: util.FormatValue(k.Open, 2), Short: true},
|
{Title: "Open", Value: k.Open.FormatString(2), Short: true},
|
||||||
{Title: "High", Value: util.FormatValue(k.High, 2), Short: true},
|
{Title: "High", Value: k.High.FormatString(2), Short: true},
|
||||||
{Title: "Low", Value: util.FormatValue(k.Low, 2), Short: true},
|
{Title: "Low", Value: k.Low.FormatString(2), Short: true},
|
||||||
{Title: "Close", Value: util.FormatValue(k.Close, 2), Short: true},
|
{Title: "Close", Value: k.Close.FormatString(2), Short: true},
|
||||||
{Title: "Mid", Value: util.FormatValue(k.Mid(), 2), Short: true},
|
{Title: "Mid", Value: k.Mid().FormatString(2), Short: true},
|
||||||
{Title: "Change", Value: util.FormatValue(k.GetChange(), 2), Short: true},
|
{Title: "Change", Value: k.GetChange().FormatString(2), Short: true},
|
||||||
{Title: "Volume", Value: util.FormatValue(k.Volume, 2), Short: true},
|
{Title: "Volume", Value: k.Volume.FormatString(2), Short: true},
|
||||||
{Title: "Taker Buy Base Volume", Value: util.FormatValue(k.TakerBuyBaseAssetVolume, 2), Short: true},
|
{Title: "Taker Buy Base Volume", Value: k.TakerBuyBaseAssetVolume.FormatString(2), Short: true},
|
||||||
{Title: "Taker Buy Quote Volume", Value: util.FormatValue(k.TakerBuyQuoteAssetVolume, 2), Short: true},
|
{Title: "Taker Buy Quote Volume", Value: k.TakerBuyQuoteAssetVolume.FormatString(2), Short: true},
|
||||||
{Title: "Max Change", Value: util.FormatValue(k.GetMaxChange(), 2), Short: true},
|
{Title: "Max Change", Value: k.GetMaxChange().FormatString(2), Short: true},
|
||||||
{
|
{
|
||||||
Title: "Thickness",
|
Title: "Thickness",
|
||||||
Value: util.FormatValue(k.GetThickness(), 4),
|
Value: k.GetThickness().FormatString(4),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Title: "UpperShadowRatio",
|
Title: "UpperShadowRatio",
|
||||||
Value: util.FormatValue(k.GetUpperShadowRatio(), 4),
|
Value: k.GetUpperShadowRatio().FormatString(4),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Title: "LowerShadowRatio",
|
Title: "LowerShadowRatio",
|
||||||
Value: util.FormatValue(k.GetLowerShadowRatio(), 4),
|
Value: k.GetLowerShadowRatio().FormatString(4),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -368,11 +368,11 @@ func (k KLineWindow) GetTrend() int {
|
||||||
|
|
||||||
func (k KLineWindow) Color() string {
|
func (k KLineWindow) Color() string {
|
||||||
if k.GetTrend() > 0 {
|
if k.GetTrend() > 0 {
|
||||||
return util.GreenColor
|
return style.GreenColor
|
||||||
} else if k.GetTrend() < 0 {
|
} else if k.GetTrend() < 0 {
|
||||||
return util.RedColor
|
return style.RedColor
|
||||||
}
|
}
|
||||||
return util.GrayColor
|
return style.GrayColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mid price
|
// Mid price
|
||||||
|
@ -491,34 +491,34 @@ func (k KLineWindow) SlackAttachment() slack.Attachment {
|
||||||
Text: fmt.Sprintf("*%s* KLineWindow %s x %d", first.Symbol, first.Interval, windowSize),
|
Text: fmt.Sprintf("*%s* KLineWindow %s x %d", first.Symbol, first.Interval, windowSize),
|
||||||
Color: k.Color(),
|
Color: k.Color(),
|
||||||
Fields: []slack.AttachmentField{
|
Fields: []slack.AttachmentField{
|
||||||
{Title: "Open", Value: util.FormatValue(k.GetOpen(), 2), Short: true},
|
{Title: "Open", Value: k.GetOpen().FormatString(2), Short: true},
|
||||||
{Title: "High", Value: util.FormatValue(k.GetHigh(), 2), Short: true},
|
{Title: "High", Value: k.GetHigh().FormatString(2), Short: true},
|
||||||
{Title: "Low", Value: util.FormatValue(k.GetLow(), 2), Short: true},
|
{Title: "Low", Value: k.GetLow().FormatString(2), Short: true},
|
||||||
{Title: "Close", Value: util.FormatValue(k.GetClose(), 2), Short: true},
|
{Title: "Close", Value: k.GetClose().FormatString(2), Short: true},
|
||||||
{Title: "Mid", Value: util.FormatValue(k.Mid(), 2), Short: true},
|
{Title: "Mid", Value: k.Mid().FormatPercentage(2), Short: true},
|
||||||
{
|
{
|
||||||
Title: "Change",
|
Title: "Change",
|
||||||
Value: util.FormatValue(k.GetChange(), 2),
|
Value: k.GetChange().FormatString(2),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Title: "Max Change",
|
Title: "Max Change",
|
||||||
Value: util.FormatValue(k.GetMaxChange(), 2),
|
Value: k.GetMaxChange().FormatString(2),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Title: "Thickness",
|
Title: "Thickness",
|
||||||
Value: util.FormatValue(k.GetThickness(), 4),
|
Value: k.GetThickness().FormatString(4),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Title: "UpperShadowRatio",
|
Title: "UpperShadowRatio",
|
||||||
Value: util.FormatValue(k.GetUpperShadowRatio(), 4),
|
Value: k.GetUpperShadowRatio().FormatString(4),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Title: "LowerShadowRatio",
|
Title: "LowerShadowRatio",
|
||||||
Value: util.FormatValue(k.GetLowerShadowRatio(), 4),
|
Value: k.GetLowerShadowRatio().FormatString(4),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/util/templateutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -384,6 +384,6 @@ func (o Order) SlackAttachment() slack.Attachment {
|
||||||
// Text: "",
|
// Text: "",
|
||||||
Fields: fields,
|
Fields: fields,
|
||||||
FooterIcon: footerIcon,
|
FooterIcon: footerIcon,
|
||||||
Footer: strings.ToLower(o.Exchange.String()) + util.Render(" creation time {{ . }}", o.CreationTime.Time().Format(time.StampMilli)),
|
Footer: strings.ToLower(o.Exchange.String()) + templateutil.Render(" creation time {{ . }}", o.CreationTime.Time().Format(time.StampMilli)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/util/templateutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PositionType string
|
type PositionType string
|
||||||
|
@ -352,7 +352,7 @@ func (p *Position) SlackAttachment() slack.Attachment {
|
||||||
color = "#DC143C"
|
color = "#DC143C"
|
||||||
}
|
}
|
||||||
|
|
||||||
title := util.Render(string(posType)+` Position {{ .Symbol }} `, p)
|
title := templateutil.Render(string(posType)+` Position {{ .Symbol }} `, p)
|
||||||
|
|
||||||
fields := []slack.AttachmentField{
|
fields := []slack.AttachmentField{
|
||||||
{Title: "Average Cost", Value: averageCost.String() + " " + p.QuoteCurrency, Short: true},
|
{Title: "Average Cost", Value: averageCost.String() + " " + p.QuoteCurrency, Short: true},
|
||||||
|
@ -378,7 +378,7 @@ func (p *Position) SlackAttachment() slack.Attachment {
|
||||||
Title: title,
|
Title: title,
|
||||||
Color: color,
|
Color: color,
|
||||||
Fields: fields,
|
Fields: fields,
|
||||||
Footer: util.Render("update time {{ . }}", time.Now().Format(time.RFC822)),
|
Footer: templateutil.Render("update time {{ . }}", time.Now().Format(time.RFC822)),
|
||||||
// FooterIcon: "",
|
// FooterIcon: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/style"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Profit struct stores the PnL information
|
// Profit struct stores the PnL information
|
||||||
|
@ -64,17 +64,17 @@ type Profit struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Profit) SlackAttachment() slack.Attachment {
|
func (p *Profit) SlackAttachment() slack.Attachment {
|
||||||
var color = util.PnLColor(p.Profit)
|
var color = style.PnLColor(p.Profit)
|
||||||
var title = fmt.Sprintf("%s PnL ", p.Symbol)
|
var title = fmt.Sprintf("%s PnL ", p.Symbol)
|
||||||
title += util.PnLEmojiMargin(p.Profit, p.ProfitMargin, util.DefaultPnLLevelResolution) + " "
|
title += style.PnLEmojiMargin(p.Profit, p.ProfitMargin, style.DefaultPnLLevelResolution) + " "
|
||||||
title += util.PnLSignString(p.Profit) + " " + p.QuoteCurrency
|
title += style.PnLSignString(p.Profit) + " " + p.QuoteCurrency
|
||||||
|
|
||||||
var fields []slack.AttachmentField
|
var fields []slack.AttachmentField
|
||||||
|
|
||||||
if !p.NetProfit.IsZero() {
|
if !p.NetProfit.IsZero() {
|
||||||
fields = append(fields, slack.AttachmentField{
|
fields = append(fields, slack.AttachmentField{
|
||||||
Title: "Net Profit",
|
Title: "Net Profit",
|
||||||
Value: util.PnLSignString(p.NetProfit) + " " + p.QuoteCurrency,
|
Value: style.PnLSignString(p.NetProfit) + " " + p.QuoteCurrency,
|
||||||
Short: true,
|
Short: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -130,9 +130,9 @@ func (p *Profit) SlackAttachment() slack.Attachment {
|
||||||
func (p *Profit) PlainText() string {
|
func (p *Profit) PlainText() string {
|
||||||
var emoji string
|
var emoji string
|
||||||
if !p.ProfitMargin.IsZero() {
|
if !p.ProfitMargin.IsZero() {
|
||||||
emoji = util.PnLEmojiMargin(p.Profit, p.ProfitMargin, util.DefaultPnLLevelResolution)
|
emoji = style.PnLEmojiMargin(p.Profit, p.ProfitMargin, style.DefaultPnLLevelResolution)
|
||||||
} else {
|
} else {
|
||||||
emoji = util.PnLEmojiSimple(p.Profit)
|
emoji = style.PnLEmojiSimple(p.Profit)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s trade profit %s %s %s (%s), net profit =~ %s %s (%s)",
|
return fmt.Sprintf("%s trade profit %s %s %s (%s), net profit =~ %s %s (%s)",
|
||||||
|
@ -220,7 +220,7 @@ func (s *ProfitStats) ResetToday() {
|
||||||
s.TodayGrossProfit = fixedpoint.Zero
|
s.TodayGrossProfit = fixedpoint.Zero
|
||||||
s.TodayGrossLoss = fixedpoint.Zero
|
s.TodayGrossLoss = fixedpoint.Zero
|
||||||
|
|
||||||
var beginningOfTheDay = util.BeginningOfTheDay(time.Now().Local())
|
var beginningOfTheDay = BeginningOfTheDay(time.Now().Local())
|
||||||
s.TodaySince = beginningOfTheDay.Unix()
|
s.TodaySince = beginningOfTheDay.Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,8 +247,8 @@ func (s *ProfitStats) PlainText() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProfitStats) SlackAttachment() slack.Attachment {
|
func (s *ProfitStats) SlackAttachment() slack.Attachment {
|
||||||
var color = util.PnLColor(s.AccumulatedPnL)
|
var color = style.PnLColor(s.AccumulatedPnL)
|
||||||
var title = fmt.Sprintf("%s Accumulated PnL %s %s", s.Symbol, util.PnLSignString(s.AccumulatedPnL), s.QuoteCurrency)
|
var title = fmt.Sprintf("%s Accumulated PnL %s %s", s.Symbol, style.PnLSignString(s.AccumulatedPnL), s.QuoteCurrency)
|
||||||
|
|
||||||
since := time.Unix(s.AccumulatedSince, 0).Local()
|
since := time.Unix(s.AccumulatedSince, 0).Local()
|
||||||
title += " Since " + since.Format(time.RFC822)
|
title += " Since " + since.Format(time.RFC822)
|
||||||
|
@ -258,7 +258,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment {
|
||||||
if !s.TodayPnL.IsZero() {
|
if !s.TodayPnL.IsZero() {
|
||||||
fields = append(fields, slack.AttachmentField{
|
fields = append(fields, slack.AttachmentField{
|
||||||
Title: "P&L Today",
|
Title: "P&L Today",
|
||||||
Value: util.PnLSignString(s.TodayPnL) + " " + s.QuoteCurrency,
|
Value: style.PnLSignString(s.TodayPnL) + " " + s.QuoteCurrency,
|
||||||
Short: true,
|
Short: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -266,7 +266,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment {
|
||||||
if !s.TodayNetProfit.IsZero() {
|
if !s.TodayNetProfit.IsZero() {
|
||||||
fields = append(fields, slack.AttachmentField{
|
fields = append(fields, slack.AttachmentField{
|
||||||
Title: "Net Profit Today",
|
Title: "Net Profit Today",
|
||||||
Value: util.PnLSignString(s.TodayNetProfit) + " " + s.QuoteCurrency,
|
Value: style.PnLSignString(s.TodayNetProfit) + " " + s.QuoteCurrency,
|
||||||
Short: true,
|
Short: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -274,7 +274,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment {
|
||||||
if !s.TodayGrossProfit.IsZero() {
|
if !s.TodayGrossProfit.IsZero() {
|
||||||
fields = append(fields, slack.AttachmentField{
|
fields = append(fields, slack.AttachmentField{
|
||||||
Title: "Gross Profit Today",
|
Title: "Gross Profit Today",
|
||||||
Value: util.PnLSignString(s.TodayGrossProfit) + " " + s.QuoteCurrency,
|
Value: style.PnLSignString(s.TodayGrossProfit) + " " + s.QuoteCurrency,
|
||||||
Short: true,
|
Short: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment {
|
||||||
if !s.TodayGrossLoss.IsZero() {
|
if !s.TodayGrossLoss.IsZero() {
|
||||||
fields = append(fields, slack.AttachmentField{
|
fields = append(fields, slack.AttachmentField{
|
||||||
Title: "Gross Loss Today",
|
Title: "Gross Loss Today",
|
||||||
Value: util.PnLSignString(s.TodayGrossLoss) + " " + s.QuoteCurrency,
|
Value: style.PnLSignString(s.TodayGrossLoss) + " " + s.QuoteCurrency,
|
||||||
Short: true,
|
Short: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -290,28 +290,28 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment {
|
||||||
if !s.AccumulatedPnL.IsZero() {
|
if !s.AccumulatedPnL.IsZero() {
|
||||||
fields = append(fields, slack.AttachmentField{
|
fields = append(fields, slack.AttachmentField{
|
||||||
Title: "Accumulated P&L",
|
Title: "Accumulated P&L",
|
||||||
Value: util.PnLSignString(s.AccumulatedPnL) + " " + s.QuoteCurrency,
|
Value: style.PnLSignString(s.AccumulatedPnL) + " " + s.QuoteCurrency,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.AccumulatedGrossProfit.IsZero() {
|
if !s.AccumulatedGrossProfit.IsZero() {
|
||||||
fields = append(fields, slack.AttachmentField{
|
fields = append(fields, slack.AttachmentField{
|
||||||
Title: "Accumulated Gross Profit",
|
Title: "Accumulated Gross Profit",
|
||||||
Value: util.PnLSignString(s.AccumulatedGrossProfit) + " " + s.QuoteCurrency,
|
Value: style.PnLSignString(s.AccumulatedGrossProfit) + " " + s.QuoteCurrency,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.AccumulatedGrossLoss.IsZero() {
|
if !s.AccumulatedGrossLoss.IsZero() {
|
||||||
fields = append(fields, slack.AttachmentField{
|
fields = append(fields, slack.AttachmentField{
|
||||||
Title: "Accumulated Gross Loss",
|
Title: "Accumulated Gross Loss",
|
||||||
Value: util.PnLSignString(s.AccumulatedGrossLoss) + " " + s.QuoteCurrency,
|
Value: style.PnLSignString(s.AccumulatedGrossLoss) + " " + s.QuoteCurrency,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.AccumulatedNetProfit.IsZero() {
|
if !s.AccumulatedNetProfit.IsZero() {
|
||||||
fields = append(fields, slack.AttachmentField{
|
fields = append(fields, slack.AttachmentField{
|
||||||
Title: "Accumulated Net Profit",
|
Title: "Accumulated Net Profit",
|
||||||
Value: util.PnLSignString(s.AccumulatedNetProfit) + " " + s.QuoteCurrency,
|
Value: style.PnLSignString(s.AccumulatedNetProfit) + " " + s.QuoteCurrency,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/style"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SideType define side type of order
|
// SideType define side type of order
|
||||||
|
@ -76,14 +76,14 @@ func (side SideType) String() string {
|
||||||
|
|
||||||
func (side SideType) Color() string {
|
func (side SideType) Color() string {
|
||||||
if side == SideTypeBuy {
|
if side == SideTypeBuy {
|
||||||
return util.GreenColor
|
return style.GreenColor
|
||||||
}
|
}
|
||||||
|
|
||||||
if side == SideTypeSell {
|
if side == SideTypeSell {
|
||||||
return util.RedColor
|
return style.RedColor
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.GrayColor
|
return style.GrayColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func SideToColorName(side SideType) string {
|
func SideToColorName(side SideType) string {
|
||||||
|
|
|
@ -7,8 +7,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var numOfDigitsOfUnixTimestamp = len(strconv.FormatInt(time.Now().Unix(), 10))
|
var numOfDigitsOfUnixTimestamp = len(strconv.FormatInt(time.Now().Unix(), 10))
|
||||||
|
@ -264,7 +262,7 @@ func ParseLooseFormatTime(s string) (LooseFormatTime, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tv, err := util.ParseTimeWithFormats(s, looseTimeFormats)
|
tv, err := ParseTimeWithFormats(s, looseTimeFormats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return LooseFormatTime{}, err
|
return LooseFormatTime{}, err
|
||||||
}
|
}
|
||||||
|
@ -294,7 +292,7 @@ func (t *LooseFormatTime) UnmarshalJSON(data []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tv, err := util.ParseTimeWithFormats(v, looseTimeFormats)
|
tv, err := ParseTimeWithFormats(v, looseTimeFormats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -340,3 +338,23 @@ func (t *Timestamp) UnmarshalJSON(o []byte) error {
|
||||||
*t = Timestamp(time.Unix(timestamp, 0))
|
*t = Timestamp(time.Unix(timestamp, 0))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseTimeWithFormats(strTime string, formats []string) (time.Time, error) {
|
||||||
|
for _, format := range formats {
|
||||||
|
tt, err := time.Parse(format, strTime)
|
||||||
|
if err == nil {
|
||||||
|
return tt, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return time.Time{}, fmt.Errorf("failed to parse time %s, valid formats are %+v", strTime, formats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BeginningOfTheDay(t time.Time) time.Time {
|
||||||
|
year, month, day := t.Date()
|
||||||
|
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Over24Hours(since time.Time) bool {
|
||||||
|
return time.Since(since) >= 24*time.Hour
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/util/templateutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -184,7 +184,7 @@ func (trade Trade) SlackAttachment() slack.Attachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
liquidity := trade.Liquidity()
|
liquidity := trade.Liquidity()
|
||||||
text := util.Render(slackTradeTextTemplate, trade)
|
text := templateutil.Render(slackTradeTextTemplate, trade)
|
||||||
footerIcon := ExchangeFooterIcon(trade.Exchange)
|
footerIcon := ExchangeFooterIcon(trade.Exchange)
|
||||||
|
|
||||||
return slack.Attachment{
|
return slack.Attachment{
|
||||||
|
@ -203,7 +203,7 @@ func (trade Trade) SlackAttachment() slack.Attachment {
|
||||||
{Title: "Order ID", Value: strconv.FormatUint(trade.OrderID, 10), Short: true},
|
{Title: "Order ID", Value: strconv.FormatUint(trade.OrderID, 10), Short: true},
|
||||||
},
|
},
|
||||||
FooterIcon: footerIcon,
|
FooterIcon: footerIcon,
|
||||||
Footer: strings.ToLower(trade.Exchange.String()) + util.Render(" creation time {{ . }}", trade.Time.Time().Format(time.StampMilli)),
|
Footer: strings.ToLower(trade.Exchange.String()) + templateutil.Render(" creation time {{ . }}", trade.Time.Time().Format(time.StampMilli)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
const GreenColor = "#228B22"
|
|
||||||
const RedColor = "#800000"
|
|
||||||
const GrayColor = "#f0f0f0"
|
|
||||||
|
|
|
@ -1,63 +1,3 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
|
||||||
)
|
|
||||||
|
|
||||||
var LossEmoji = "🔥"
|
|
||||||
var ProfitEmoji = "💰"
|
|
||||||
var DefaultPnLLevelResolution = fixedpoint.NewFromFloat(0.001)
|
|
||||||
|
|
||||||
func PnLColor(pnl fixedpoint.Value) string {
|
|
||||||
if pnl.Sign() > 0 {
|
|
||||||
return GreenColor
|
|
||||||
}
|
|
||||||
return RedColor
|
|
||||||
}
|
|
||||||
|
|
||||||
func PnLSignString(pnl fixedpoint.Value) string {
|
|
||||||
if pnl.Sign() > 0 {
|
|
||||||
return "+" + pnl.String()
|
|
||||||
}
|
|
||||||
return pnl.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func PnLEmojiSimple(pnl fixedpoint.Value) string {
|
|
||||||
if pnl.Sign() < 0 {
|
|
||||||
return LossEmoji
|
|
||||||
}
|
|
||||||
|
|
||||||
if pnl.IsZero() {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return ProfitEmoji
|
|
||||||
}
|
|
||||||
|
|
||||||
func PnLEmojiMargin(pnl, margin, resolution fixedpoint.Value) (out string) {
|
|
||||||
if margin.IsZero() {
|
|
||||||
return PnLEmojiSimple(pnl)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pnl.Sign() < 0 {
|
|
||||||
out = LossEmoji
|
|
||||||
level := (margin.Neg()).Div(resolution).Int()
|
|
||||||
for i := 1; i < level; i++ {
|
|
||||||
out += LossEmoji
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
if pnl.IsZero() {
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
out = ProfitEmoji
|
|
||||||
level := margin.Div(resolution).Int()
|
|
||||||
for i := 1; i < level; i++ {
|
|
||||||
out += ProfitEmoji
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,2 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Render(tpl string, args interface{}) string {
|
|
||||||
var buf = bytes.NewBuffer(nil)
|
|
||||||
tmpl, err := template.New("tmp").Parse(tpl)
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Error("template parse error")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tmpl.Execute(buf, args)
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Error("template execute error")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
25
pkg/util/templateutil/render.go
Normal file
25
pkg/util/templateutil/render.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package templateutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Render(tpl string, args interface{}) string {
|
||||||
|
var buf = bytes.NewBuffer(nil)
|
||||||
|
tmpl, err := template.New("tmp").Parse(tpl)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("template parse error")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tmpl.Execute(buf, args)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("template execute error")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -11,25 +10,7 @@ func MillisecondsJitter(d time.Duration, jitterInMilliseconds int) time.Duration
|
||||||
return d + time.Duration(n)*time.Millisecond
|
return d + time.Duration(n)*time.Millisecond
|
||||||
}
|
}
|
||||||
|
|
||||||
func BeginningOfTheDay(t time.Time) time.Time {
|
|
||||||
year, month, day := t.Date()
|
|
||||||
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func Over24Hours(since time.Time) bool {
|
|
||||||
return time.Since(since) >= 24*time.Hour
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnixMilli() int64 {
|
func UnixMilli() int64 {
|
||||||
return time.Now().UnixNano() / int64(time.Millisecond)
|
return time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseTimeWithFormats(strTime string, formats []string) (time.Time, error) {
|
|
||||||
for _, format := range formats {
|
|
||||||
tt, err := time.Parse(format, strTime)
|
|
||||||
if err == nil {
|
|
||||||
return tt, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return time.Time{}, fmt.Errorf("failed to parse time %s, valid formats are %+v", strTime, formats)
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user