mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 01:01:56 +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/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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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() + ":"
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
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 (
|
||||
"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 {
|
|
@ -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()...)
|
||||
}
|
||||
|
|
|
@ -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: "",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
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/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,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: "",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,2 @@
|
|||
package util
|
||||
|
||||
const GreenColor = "#228B22"
|
||||
const RedColor = "#800000"
|
||||
const GrayColor = "#f0f0f0"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
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
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user