From c86b29e6dcff16127b7f7c232ee215d34994fa0e Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 23 Aug 2022 02:12:26 +0800 Subject: [PATCH] all: resolve import cycle --- pkg/bbgo/environment.go | 7 +-- pkg/bbgo/interact.go | 3 +- pkg/bbgo/persistence.go | 4 +- pkg/bbgo/persistence_test.go | 6 +-- pkg/bbgo/reflect.go | 30 ------------- pkg/bbgo/session.go | 3 +- pkg/bbgo/trader.go | 4 +- pkg/dynamic/id.go | 30 +++++++++++++ pkg/{util => dynamic}/print_config.go | 25 +++-------- pkg/strategy/drift/output.go | 10 ++--- pkg/strategy/xbalance/strategy.go | 11 ++--- pkg/strategy/xnav/strategy.go | 9 ++-- pkg/style/colors.go | 5 +++ pkg/style/pnl.go | 61 +++++++++++++++++++++++++++ pkg/style/table.go | 21 +++++++++ pkg/types/colors.go | 2 + pkg/types/instance.go | 5 +++ pkg/types/kline.go | 60 +++++++++++++------------- pkg/types/order.go | 4 +- pkg/types/position.go | 6 +-- pkg/types/profit.go | 36 ++++++++-------- pkg/types/side.go | 8 ++-- pkg/types/time.go | 26 ++++++++++-- pkg/types/trade.go | 6 +-- pkg/util/colors.go | 3 -- pkg/util/emoji.go | 60 -------------------------- pkg/util/render.go | 23 ---------- pkg/util/templateutil/render.go | 25 +++++++++++ pkg/util/time.go | 19 --------- 29 files changed, 267 insertions(+), 245 deletions(-) create mode 100644 pkg/dynamic/id.go rename pkg/{util => dynamic}/print_config.go (82%) create mode 100644 pkg/style/colors.go create mode 100644 pkg/style/pnl.go create mode 100644 pkg/style/table.go create mode 100644 pkg/types/colors.go create mode 100644 pkg/types/instance.go create mode 100644 pkg/util/templateutil/render.go diff --git a/pkg/bbgo/environment.go b/pkg/bbgo/environment.go index c60f05680..06ce8f494 100644 --- a/pkg/bbgo/environment.go +++ b/pkg/bbgo/environment.go @@ -30,6 +30,7 @@ import ( "github.com/c9s/bbgo/pkg/slack/slacklog" "github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/util" + "github.com/c9s/bbgo/pkg/util/templateutil" ) func init() { @@ -366,7 +367,7 @@ func (environ *Environment) ConfigureNotificationRouting(conf *NotificationConfi case "$session": defaultOrderUpdateHandler := func(order types.Order) { - text := util.Render(TemplateOrderReport, order) + text := templateutil.Render(TemplateOrderReport, order) Notify(text, &order) } for name := range environ.sessions { @@ -376,7 +377,7 @@ func (environ *Environment) ConfigureNotificationRouting(conf *NotificationConfi channel, ok := Notification.SessionChannelRouter.Route(name) if ok { session.UserDataStream.OnOrderUpdate(func(order types.Order) { - text := util.Render(TemplateOrderReport, order) + text := templateutil.Render(TemplateOrderReport, order) NotifyTo(channel, text, &order) }) } else { @@ -397,7 +398,7 @@ func (environ *Environment) ConfigureNotificationRouting(conf *NotificationConfi // use same handler for each session handler := func(order types.Order) { - text := util.Render(TemplateOrderReport, order) + text := templateutil.Render(TemplateOrderReport, order) channel, ok := Notification.RouteObject(&order) if ok { NotifyTo(channel, text, &order) diff --git a/pkg/bbgo/interact.go b/pkg/bbgo/interact.go index 389402d9a..f5b776861 100644 --- a/pkg/bbgo/interact.go +++ b/pkg/bbgo/interact.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/c9s/bbgo/pkg/dynamic" "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/interact" "github.com/c9s/bbgo/pkg/types" @@ -526,7 +527,7 @@ func (it *CoreInteraction) Initialize() error { // getStrategySignature returns strategy instance unique signature func getStrategySignature(strategy SingleExchangeStrategy) (string, error) { // Returns instance ID - var signature = CallID(strategy) + var signature = dynamic.CallID(strategy) if signature != "" { return signature, nil } diff --git a/pkg/bbgo/persistence.go b/pkg/bbgo/persistence.go index e4232035e..a9335fc08 100644 --- a/pkg/bbgo/persistence.go +++ b/pkg/bbgo/persistence.go @@ -82,7 +82,7 @@ func (p *Persistence) Save(val interface{}, subIDs ...string) error { } func (p *Persistence) Sync(obj interface{}) error { - id := CallID(obj) + id := dynamic.CallID(obj) if len(id) == 0 { return nil } @@ -93,7 +93,7 @@ func (p *Persistence) Sync(obj interface{}) error { // Sync syncs the object properties into the persistence layer func Sync(obj interface{}) { - id := CallID(obj) + id := dynamic.CallID(obj) if len(id) == 0 { log.Warnf("InstanceID() is not provided, can not sync persistence") return diff --git a/pkg/bbgo/persistence_test.go b/pkg/bbgo/persistence_test.go index 843ce6777..dbd612ff2 100644 --- a/pkg/bbgo/persistence_test.go +++ b/pkg/bbgo/persistence_test.go @@ -57,13 +57,13 @@ func preparePersistentServices() []service.PersistenceService { func Test_CallID(t *testing.T) { t.Run("default", func(t *testing.T) { - id := CallID(&TestStruct{}) + id := dynamic.CallID(&TestStruct{}) assert.NotEmpty(t, id) assert.Equal(t, "test-struct", id) }) 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) }) } @@ -121,7 +121,7 @@ func Test_storePersistenceFields(t *testing.T) { for _, ps := range pss { psName := reflect.TypeOf(ps).Elem().String() t.Run("all/"+psName, func(t *testing.T) { - id := CallID(a) + id := dynamic.CallID(a) err := storePersistenceFields(a, id, ps) assert.NoError(t, err) diff --git a/pkg/bbgo/reflect.go b/pkg/bbgo/reflect.go index f4662b44f..920078f66 100644 --- a/pkg/bbgo/reflect.go +++ b/pkg/bbgo/reflect.go @@ -1,32 +1,2 @@ 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() + ":" -} diff --git a/pkg/bbgo/session.go b/pkg/bbgo/session.go index 1e59f602e..747494509 100644 --- a/pkg/bbgo/session.go +++ b/pkg/bbgo/session.go @@ -14,6 +14,7 @@ import ( "github.com/spf13/viper" "github.com/c9s/bbgo/pkg/cache" + "github.com/c9s/bbgo/pkg/util/templateutil" exchange2 "github.com/c9s/bbgo/pkg/exchange" "github.com/c9s/bbgo/pkg/fixedpoint" @@ -848,7 +849,7 @@ func (session *ExchangeSession) SlackAttachment() slack.Attachment { Title: session.Name, Fields: fields, FooterIcon: footerIcon, - Footer: util.Render("update time {{ . }}", time.Now().Format(time.RFC822)), + Footer: templateutil.Render("update time {{ . }}", time.Now().Format(time.RFC822)), } } diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index ad9a4ec07..0db80870f 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -384,7 +384,7 @@ func (trader *Trader) LoadState() error { log.Infof("loading strategies states...") return trader.IterateStrategies(func(strategy StrategyID) error { - id := CallID(strategy) + id := dynamic.CallID(strategy) return loadPersistenceFields(strategy, id, ps) }) } @@ -420,7 +420,7 @@ func (trader *Trader) SaveState() error { log.Infof("saving strategies states...") return trader.IterateStrategies(func(strategy StrategyID) error { - id := CallID(strategy) + id := dynamic.CallID(strategy) if len(id) == 0 { return nil } diff --git a/pkg/dynamic/id.go b/pkg/dynamic/id.go new file mode 100644 index 000000000..b7b4217e6 --- /dev/null +++ b/pkg/dynamic/id.go @@ -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() + ":" +} diff --git a/pkg/util/print_config.go b/pkg/dynamic/print_config.go similarity index 82% rename from pkg/util/print_config.go rename to pkg/dynamic/print_config.go index 715832c51..85052ca2d 100644 --- a/pkg/util/print_config.go +++ b/pkg/dynamic/print_config.go @@ -1,4 +1,4 @@ -package util +package dynamic import ( "fmt" @@ -11,25 +11,10 @@ import ( "github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/text" - "github.com/c9s/bbgo/pkg/bbgo" "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 { 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"}) } - write(f, "---- %s Settings ---\n", bbgo.CallID(s)) + write(f, "---- %s Settings ---\n", CallID(s)) embeddedWhiteSet := map[string]struct{}{} for _, whiteList := range whiteLists { @@ -71,7 +56,7 @@ func PrintConfig(s interface{}, f io.Writer, style *table.Style, withColor bool, val := reflect.ValueOf(s) - if val.Type().Kind() == Pointer { + if val.Type().Kind() == util.Pointer { val = val.Elem() } var values types.JsonArr @@ -88,7 +73,7 @@ func PrintConfig(s interface{}, f io.Writer, style *table.Style, withColor bool, if t.Anonymous { var target reflect.Type var field reflect.Value - if t.Type.Kind() == Pointer { + if t.Type.Kind() == util.Pointer { target = t.Type.Elem() field = val.Field(i).Elem() } else { diff --git a/pkg/strategy/drift/output.go b/pkg/strategy/drift/output.go index c78875a45..e3f2d21f9 100644 --- a/pkg/strategy/drift/output.go +++ b/pkg/strategy/drift/output.go @@ -6,10 +6,10 @@ import ( "reflect" "unsafe" - "github.com/c9s/bbgo/pkg/dynamic" - "github.com/c9s/bbgo/pkg/util" - "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) { @@ -95,7 +95,7 @@ func (s *Strategy) ParamDump(f io.Writer, seriesLength ...int) { func (s *Strategy) Print(f io.Writer, pretty bool, withColor ...bool) { var style *table.Style 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()...) } diff --git a/pkg/strategy/xbalance/strategy.go b/pkg/strategy/xbalance/strategy.go index ea4aa1e04..b4655ce16 100644 --- a/pkg/strategy/xbalance/strategy.go +++ b/pkg/strategy/xbalance/strategy.go @@ -15,6 +15,7 @@ import ( "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/util" + "github.com/c9s/bbgo/pkg/util/templateutil" ) const ID = "xbalance" @@ -39,7 +40,7 @@ func (s *State) IsOver24Hours() bool { } func (s *State) PlainText() string { - return util.Render(`{{ .Asset }} transfer stats: + return templateutil.Render(`{{ .Asset }} transfer stats: daily number of transfers: {{ .DailyNumberOfTransfers }} 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 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() { - var beginningOfTheDay = util.BeginningOfTheDay(time.Now().Local()) + var beginningOfTheDay = types.BeginningOfTheDay(time.Now().Local()) *s = State{ DailyNumberOfTransfers: 0, DailyAmountOfTransfers: fixedpoint.Zero, @@ -93,7 +94,7 @@ func (r *WithdrawalRequest) PlainText() string { func (r *WithdrawalRequest) SlackAttachment() slack.Attachment { var color = "#DC143C" - title := util.Render(`Withdraw Request {{ .Asset }}`, r) + title := templateutil.Render(`Withdraw Request {{ .Asset }}`, r) return slack.Attachment{ // Pretext: "", // Text: text, @@ -105,7 +106,7 @@ func (r *WithdrawalRequest) SlackAttachment() slack.Attachment { {Title: "From", Value: r.FromSession}, {Title: "To", Value: r.ToSession}, }, - Footer: util.Render("Time {{ . }}", time.Now().Format(time.RFC822)), + Footer: templateutil.Render("Time {{ . }}", time.Now().Format(time.RFC822)), // FooterIcon: "", } } diff --git a/pkg/strategy/xnav/strategy.go b/pkg/strategy/xnav/strategy.go index ea9526f47..28381a7d6 100644 --- a/pkg/strategy/xnav/strategy.go +++ b/pkg/strategy/xnav/strategy.go @@ -6,6 +6,7 @@ import ( "time" "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/util/templateutil" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -32,11 +33,11 @@ type State struct { } 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 { - return util.Render(`{{ .Asset }} transfer stats: + return templateutil.Render(`{{ .Asset }} transfer stats: daily number of transfers: {{ .DailyNumberOfTransfers }} daily amount of transfers {{ .DailyAmountOfTransfers.Float64 }}`, s) } @@ -46,12 +47,12 @@ func (s *State) SlackAttachment() slack.Attachment { // Pretext: "", // Text: text, 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() { - var beginningOfTheDay = util.BeginningOfTheDay(time.Now().Local()) + var beginningOfTheDay = types.BeginningOfTheDay(time.Now().Local()) *s = State{ Since: beginningOfTheDay.Unix(), } diff --git a/pkg/style/colors.go b/pkg/style/colors.go new file mode 100644 index 000000000..f9447fcd0 --- /dev/null +++ b/pkg/style/colors.go @@ -0,0 +1,5 @@ +package style + +const GreenColor = "#228B22" +const RedColor = "#800000" +const GrayColor = "#f0f0f0" diff --git a/pkg/style/pnl.go b/pkg/style/pnl.go new file mode 100644 index 000000000..8f9a68edd --- /dev/null +++ b/pkg/style/pnl.go @@ -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 +} diff --git a/pkg/style/table.go b/pkg/style/table.go new file mode 100644 index 000000000..15f426d17 --- /dev/null +++ b/pkg/style/table.go @@ -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 +} diff --git a/pkg/types/colors.go b/pkg/types/colors.go new file mode 100644 index 000000000..5bb13ecf9 --- /dev/null +++ b/pkg/types/colors.go @@ -0,0 +1,2 @@ +package types + diff --git a/pkg/types/instance.go b/pkg/types/instance.go new file mode 100644 index 000000000..ac4f26e29 --- /dev/null +++ b/pkg/types/instance.go @@ -0,0 +1,5 @@ +package types + +type InstanceIDProvider interface { + InstanceID() string +} diff --git a/pkg/types/kline.go b/pkg/types/kline.go index 5444c8e3d..9cb4bcad6 100644 --- a/pkg/types/kline.go +++ b/pkg/types/kline.go @@ -7,7 +7,7 @@ import ( "github.com/slack-go/slack" "github.com/c9s/bbgo/pkg/fixedpoint" - "github.com/c9s/bbgo/pkg/util" + "github.com/c9s/bbgo/pkg/style" ) type Direction int @@ -214,11 +214,11 @@ func (k KLine) GetChange() fixedpoint.Value { func (k KLine) Color() string { if k.Direction() > 0 { - return util.GreenColor + return style.GreenColor } else if k.Direction() < 0 { - return util.RedColor + return style.RedColor } - return util.GrayColor + return style.GrayColor } 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), Color: k.Color(), Fields: []slack.AttachmentField{ - {Title: "Open", Value: util.FormatValue(k.Open, 2), Short: true}, - {Title: "High", Value: util.FormatValue(k.High, 2), Short: true}, - {Title: "Low", Value: util.FormatValue(k.Low, 2), Short: true}, - {Title: "Close", Value: util.FormatValue(k.Close, 2), Short: true}, - {Title: "Mid", Value: util.FormatValue(k.Mid(), 2), Short: true}, - {Title: "Change", Value: util.FormatValue(k.GetChange(), 2), Short: true}, - {Title: "Volume", Value: util.FormatValue(k.Volume, 2), Short: true}, - {Title: "Taker Buy Base Volume", Value: util.FormatValue(k.TakerBuyBaseAssetVolume, 2), Short: true}, - {Title: "Taker Buy Quote Volume", Value: util.FormatValue(k.TakerBuyQuoteAssetVolume, 2), Short: true}, - {Title: "Max Change", Value: util.FormatValue(k.GetMaxChange(), 2), Short: true}, + {Title: "Open", Value: k.Open.FormatString(2), Short: true}, + {Title: "High", Value: k.High.FormatString(2), Short: true}, + {Title: "Low", Value: k.Low.FormatString(2), Short: true}, + {Title: "Close", Value: k.Close.FormatString(2), Short: true}, + {Title: "Mid", Value: k.Mid().FormatString(2), Short: true}, + {Title: "Change", Value: k.GetChange().FormatString(2), Short: true}, + {Title: "Volume", Value: k.Volume.FormatString(2), Short: true}, + {Title: "Taker Buy Base Volume", Value: k.TakerBuyBaseAssetVolume.FormatString(2), Short: true}, + {Title: "Taker Buy Quote Volume", Value: k.TakerBuyQuoteAssetVolume.FormatString(2), Short: true}, + {Title: "Max Change", Value: k.GetMaxChange().FormatString(2), Short: true}, { Title: "Thickness", - Value: util.FormatValue(k.GetThickness(), 4), + Value: k.GetThickness().FormatString(4), Short: true, }, { Title: "UpperShadowRatio", - Value: util.FormatValue(k.GetUpperShadowRatio(), 4), + Value: k.GetUpperShadowRatio().FormatString(4), Short: true, }, { Title: "LowerShadowRatio", - Value: util.FormatValue(k.GetLowerShadowRatio(), 4), + Value: k.GetLowerShadowRatio().FormatString(4), Short: true, }, }, @@ -368,11 +368,11 @@ func (k KLineWindow) GetTrend() int { func (k KLineWindow) Color() string { if k.GetTrend() > 0 { - return util.GreenColor + return style.GreenColor } else if k.GetTrend() < 0 { - return util.RedColor + return style.RedColor } - return util.GrayColor + return style.GrayColor } // 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), Color: k.Color(), Fields: []slack.AttachmentField{ - {Title: "Open", Value: util.FormatValue(k.GetOpen(), 2), Short: true}, - {Title: "High", Value: util.FormatValue(k.GetHigh(), 2), Short: true}, - {Title: "Low", Value: util.FormatValue(k.GetLow(), 2), Short: true}, - {Title: "Close", Value: util.FormatValue(k.GetClose(), 2), Short: true}, - {Title: "Mid", Value: util.FormatValue(k.Mid(), 2), Short: true}, + {Title: "Open", Value: k.GetOpen().FormatString(2), Short: true}, + {Title: "High", Value: k.GetHigh().FormatString(2), Short: true}, + {Title: "Low", Value: k.GetLow().FormatString(2), Short: true}, + {Title: "Close", Value: k.GetClose().FormatString(2), Short: true}, + {Title: "Mid", Value: k.Mid().FormatPercentage(2), Short: true}, { Title: "Change", - Value: util.FormatValue(k.GetChange(), 2), + Value: k.GetChange().FormatString(2), Short: true, }, { Title: "Max Change", - Value: util.FormatValue(k.GetMaxChange(), 2), + Value: k.GetMaxChange().FormatString(2), Short: true, }, { Title: "Thickness", - Value: util.FormatValue(k.GetThickness(), 4), + Value: k.GetThickness().FormatString(4), Short: true, }, { Title: "UpperShadowRatio", - Value: util.FormatValue(k.GetUpperShadowRatio(), 4), + Value: k.GetUpperShadowRatio().FormatString(4), Short: true, }, { Title: "LowerShadowRatio", - Value: util.FormatValue(k.GetLowerShadowRatio(), 4), + Value: k.GetLowerShadowRatio().FormatString(4), Short: true, }, }, diff --git a/pkg/types/order.go b/pkg/types/order.go index 2d43de1ad..97587f9e1 100644 --- a/pkg/types/order.go +++ b/pkg/types/order.go @@ -11,7 +11,7 @@ import ( "github.com/slack-go/slack" "github.com/c9s/bbgo/pkg/fixedpoint" - "github.com/c9s/bbgo/pkg/util" + "github.com/c9s/bbgo/pkg/util/templateutil" ) func init() { @@ -384,6 +384,6 @@ func (o Order) SlackAttachment() slack.Attachment { // Text: "", Fields: fields, 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)), } } diff --git a/pkg/types/position.go b/pkg/types/position.go index 4b68d48a3..536f00728 100644 --- a/pkg/types/position.go +++ b/pkg/types/position.go @@ -8,7 +8,7 @@ import ( "github.com/slack-go/slack" "github.com/c9s/bbgo/pkg/fixedpoint" - "github.com/c9s/bbgo/pkg/util" + "github.com/c9s/bbgo/pkg/util/templateutil" ) type PositionType string @@ -352,7 +352,7 @@ func (p *Position) SlackAttachment() slack.Attachment { color = "#DC143C" } - title := util.Render(string(posType)+` Position {{ .Symbol }} `, p) + title := templateutil.Render(string(posType)+` Position {{ .Symbol }} `, p) fields := []slack.AttachmentField{ {Title: "Average Cost", Value: averageCost.String() + " " + p.QuoteCurrency, Short: true}, @@ -378,7 +378,7 @@ func (p *Position) SlackAttachment() slack.Attachment { Title: title, Color: color, Fields: fields, - Footer: util.Render("update time {{ . }}", time.Now().Format(time.RFC822)), + Footer: templateutil.Render("update time {{ . }}", time.Now().Format(time.RFC822)), // FooterIcon: "", } } diff --git a/pkg/types/profit.go b/pkg/types/profit.go index 73da1ed21..a851c4836 100644 --- a/pkg/types/profit.go +++ b/pkg/types/profit.go @@ -7,7 +7,7 @@ import ( "github.com/slack-go/slack" "github.com/c9s/bbgo/pkg/fixedpoint" - "github.com/c9s/bbgo/pkg/util" + "github.com/c9s/bbgo/pkg/style" ) // Profit struct stores the PnL information @@ -64,17 +64,17 @@ type Profit struct { } 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) - title += util.PnLEmojiMargin(p.Profit, p.ProfitMargin, util.DefaultPnLLevelResolution) + " " - title += util.PnLSignString(p.Profit) + " " + p.QuoteCurrency + title += style.PnLEmojiMargin(p.Profit, p.ProfitMargin, style.DefaultPnLLevelResolution) + " " + title += style.PnLSignString(p.Profit) + " " + p.QuoteCurrency var fields []slack.AttachmentField if !p.NetProfit.IsZero() { fields = append(fields, slack.AttachmentField{ Title: "Net Profit", - Value: util.PnLSignString(p.NetProfit) + " " + p.QuoteCurrency, + Value: style.PnLSignString(p.NetProfit) + " " + p.QuoteCurrency, Short: true, }) } @@ -130,9 +130,9 @@ func (p *Profit) SlackAttachment() slack.Attachment { func (p *Profit) PlainText() string { var emoji string if !p.ProfitMargin.IsZero() { - emoji = util.PnLEmojiMargin(p.Profit, p.ProfitMargin, util.DefaultPnLLevelResolution) + emoji = style.PnLEmojiMargin(p.Profit, p.ProfitMargin, style.DefaultPnLLevelResolution) } 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)", @@ -220,7 +220,7 @@ func (s *ProfitStats) ResetToday() { s.TodayGrossProfit = fixedpoint.Zero s.TodayGrossLoss = fixedpoint.Zero - var beginningOfTheDay = util.BeginningOfTheDay(time.Now().Local()) + var beginningOfTheDay = BeginningOfTheDay(time.Now().Local()) s.TodaySince = beginningOfTheDay.Unix() } @@ -247,8 +247,8 @@ func (s *ProfitStats) PlainText() string { } func (s *ProfitStats) SlackAttachment() slack.Attachment { - var color = util.PnLColor(s.AccumulatedPnL) - var title = fmt.Sprintf("%s Accumulated PnL %s %s", s.Symbol, util.PnLSignString(s.AccumulatedPnL), s.QuoteCurrency) + var color = style.PnLColor(s.AccumulatedPnL) + var title = fmt.Sprintf("%s Accumulated PnL %s %s", s.Symbol, style.PnLSignString(s.AccumulatedPnL), s.QuoteCurrency) since := time.Unix(s.AccumulatedSince, 0).Local() title += " Since " + since.Format(time.RFC822) @@ -258,7 +258,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment { if !s.TodayPnL.IsZero() { fields = append(fields, slack.AttachmentField{ Title: "P&L Today", - Value: util.PnLSignString(s.TodayPnL) + " " + s.QuoteCurrency, + Value: style.PnLSignString(s.TodayPnL) + " " + s.QuoteCurrency, Short: true, }) } @@ -266,7 +266,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment { if !s.TodayNetProfit.IsZero() { fields = append(fields, slack.AttachmentField{ Title: "Net Profit Today", - Value: util.PnLSignString(s.TodayNetProfit) + " " + s.QuoteCurrency, + Value: style.PnLSignString(s.TodayNetProfit) + " " + s.QuoteCurrency, Short: true, }) } @@ -274,7 +274,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment { if !s.TodayGrossProfit.IsZero() { fields = append(fields, slack.AttachmentField{ Title: "Gross Profit Today", - Value: util.PnLSignString(s.TodayGrossProfit) + " " + s.QuoteCurrency, + Value: style.PnLSignString(s.TodayGrossProfit) + " " + s.QuoteCurrency, Short: true, }) } @@ -282,7 +282,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment { if !s.TodayGrossLoss.IsZero() { fields = append(fields, slack.AttachmentField{ Title: "Gross Loss Today", - Value: util.PnLSignString(s.TodayGrossLoss) + " " + s.QuoteCurrency, + Value: style.PnLSignString(s.TodayGrossLoss) + " " + s.QuoteCurrency, Short: true, }) } @@ -290,28 +290,28 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment { if !s.AccumulatedPnL.IsZero() { fields = append(fields, slack.AttachmentField{ Title: "Accumulated P&L", - Value: util.PnLSignString(s.AccumulatedPnL) + " " + s.QuoteCurrency, + Value: style.PnLSignString(s.AccumulatedPnL) + " " + s.QuoteCurrency, }) } if !s.AccumulatedGrossProfit.IsZero() { fields = append(fields, slack.AttachmentField{ Title: "Accumulated Gross Profit", - Value: util.PnLSignString(s.AccumulatedGrossProfit) + " " + s.QuoteCurrency, + Value: style.PnLSignString(s.AccumulatedGrossProfit) + " " + s.QuoteCurrency, }) } if !s.AccumulatedGrossLoss.IsZero() { fields = append(fields, slack.AttachmentField{ Title: "Accumulated Gross Loss", - Value: util.PnLSignString(s.AccumulatedGrossLoss) + " " + s.QuoteCurrency, + Value: style.PnLSignString(s.AccumulatedGrossLoss) + " " + s.QuoteCurrency, }) } if !s.AccumulatedNetProfit.IsZero() { fields = append(fields, slack.AttachmentField{ Title: "Accumulated Net Profit", - Value: util.PnLSignString(s.AccumulatedNetProfit) + " " + s.QuoteCurrency, + Value: style.PnLSignString(s.AccumulatedNetProfit) + " " + s.QuoteCurrency, }) } diff --git a/pkg/types/side.go b/pkg/types/side.go index 4eef24665..75e7a9ae1 100644 --- a/pkg/types/side.go +++ b/pkg/types/side.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" - "github.com/c9s/bbgo/pkg/util" + "github.com/c9s/bbgo/pkg/style" ) // SideType define side type of order @@ -76,14 +76,14 @@ func (side SideType) String() string { func (side SideType) Color() string { if side == SideTypeBuy { - return util.GreenColor + return style.GreenColor } if side == SideTypeSell { - return util.RedColor + return style.RedColor } - return util.GrayColor + return style.GrayColor } func SideToColorName(side SideType) string { diff --git a/pkg/types/time.go b/pkg/types/time.go index 683bf8df2..5bc811c85 100644 --- a/pkg/types/time.go +++ b/pkg/types/time.go @@ -7,8 +7,6 @@ import ( "strconv" "strings" "time" - - "github.com/c9s/bbgo/pkg/util" ) 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 { return LooseFormatTime{}, err } @@ -294,7 +292,7 @@ func (t *LooseFormatTime) UnmarshalJSON(data []byte) error { return err } - tv, err := util.ParseTimeWithFormats(v, looseTimeFormats) + tv, err := ParseTimeWithFormats(v, looseTimeFormats) if err != nil { return err } @@ -340,3 +338,23 @@ func (t *Timestamp) UnmarshalJSON(o []byte) error { *t = Timestamp(time.Unix(timestamp, 0)) 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 +} + diff --git a/pkg/types/trade.go b/pkg/types/trade.go index d8908fce8..0db78b6ef 100644 --- a/pkg/types/trade.go +++ b/pkg/types/trade.go @@ -11,7 +11,7 @@ import ( "github.com/slack-go/slack" "github.com/c9s/bbgo/pkg/fixedpoint" - "github.com/c9s/bbgo/pkg/util" + "github.com/c9s/bbgo/pkg/util/templateutil" ) func init() { @@ -184,7 +184,7 @@ func (trade Trade) SlackAttachment() slack.Attachment { } liquidity := trade.Liquidity() - text := util.Render(slackTradeTextTemplate, trade) + text := templateutil.Render(slackTradeTextTemplate, trade) footerIcon := ExchangeFooterIcon(trade.Exchange) return slack.Attachment{ @@ -203,7 +203,7 @@ func (trade Trade) SlackAttachment() slack.Attachment { {Title: "Order ID", Value: strconv.FormatUint(trade.OrderID, 10), Short: true}, }, 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)), } } diff --git a/pkg/util/colors.go b/pkg/util/colors.go index b17b407eb..a5e4c54fa 100644 --- a/pkg/util/colors.go +++ b/pkg/util/colors.go @@ -1,5 +1,2 @@ package util -const GreenColor = "#228B22" -const RedColor = "#800000" -const GrayColor = "#f0f0f0" diff --git a/pkg/util/emoji.go b/pkg/util/emoji.go index 354718252..90272ebc9 100644 --- a/pkg/util/emoji.go +++ b/pkg/util/emoji.go @@ -1,63 +1,3 @@ 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 -} diff --git a/pkg/util/render.go b/pkg/util/render.go index 69ccb78b2..a5e4c54fa 100644 --- a/pkg/util/render.go +++ b/pkg/util/render.go @@ -1,25 +1,2 @@ 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() -} diff --git a/pkg/util/templateutil/render.go b/pkg/util/templateutil/render.go new file mode 100644 index 000000000..8e19fc1b2 --- /dev/null +++ b/pkg/util/templateutil/render.go @@ -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() +} diff --git a/pkg/util/time.go b/pkg/util/time.go index d95d1a9a1..df1ac8d88 100644 --- a/pkg/util/time.go +++ b/pkg/util/time.go @@ -1,7 +1,6 @@ package util import ( - "fmt" "math/rand" "time" ) @@ -11,25 +10,7 @@ func MillisecondsJitter(d time.Duration, jitterInMilliseconds int) time.Duration 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 { 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) -}