diff --git a/pkg/bbgo/session.go b/pkg/bbgo/session.go index 15bbced7c..53b8d6e0e 100644 --- a/pkg/bbgo/session.go +++ b/pkg/bbgo/session.go @@ -649,9 +649,10 @@ func (session *ExchangeSession) FormatOrder(order types.SubmitOrder) (types.Subm } func (session *ExchangeSession) UpdatePrices(ctx context.Context, currencies []string, fiat string) (err error) { - if session.lastPriceUpdatedAt.After(time.Now().Add(-time.Hour)) { - return nil - } + // TODO: move this cache check to the http routes + // if session.lastPriceUpdatedAt.After(time.Now().Add(-time.Hour)) { + // return nil + // } var symbols []string for _, c := range currencies { diff --git a/pkg/strategy/xnav/strategy.go b/pkg/strategy/xnav/strategy.go index 03eeafde5..5ac73a050 100644 --- a/pkg/strategy/xnav/strategy.go +++ b/pkg/strategy/xnav/strategy.go @@ -78,7 +78,6 @@ var Ten = fixedpoint.NewFromInt(10) func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {} func (s *Strategy) recordNetAssetValue(ctx context.Context, sessions map[string]*bbgo.ExchangeSession) { - totalAssets := types.AssetMap{} totalBalances := types.BalanceMap{} allPrices := map[string]fixedpoint.Value{} sessionBalances := map[string]types.BalanceMap{} @@ -113,19 +112,20 @@ func (s *Strategy) recordNetAssetValue(ctx context.Context, sessions map[string] s.Environment.RecordAsset(priceTime, session, assets) } - allAssets := totalBalances.Assets(allPrices, priceTime) - for currency, asset := range allAssets { + displayAssets := types.AssetMap{} + totalAssets := totalBalances.Assets(allPrices, priceTime) + s.Environment.RecordAsset(priceTime, &bbgo.ExchangeSession{Name: "ALL"}, totalAssets) + + for currency, asset := range totalAssets { // calculated if it's dust only when InUSD (usd value) is defined. if s.IgnoreDusts && !asset.InUSD.IsZero() && asset.InUSD.Compare(Ten) < 0 { continue } - totalAssets[currency] = asset + displayAssets[currency] = asset } - s.Environment.RecordAsset(priceTime, &bbgo.ExchangeSession{Name: "ALL"}, totalAssets) - - s.Notifiability.Notify(totalAssets) + s.Notifiability.Notify(displayAssets) if s.state != nil { if s.state.IsOver24Hours() { @@ -196,7 +196,7 @@ func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, se } // TODO: if interval is supported, we can use kline as the ticker - if _, ok := types.SupportedIntervals[s.Interval] ; ok { + if _, ok := types.SupportedIntervals[s.Interval]; ok { } diff --git a/pkg/types/account.go b/pkg/types/account.go index 14dff0cec..40e89088d 100644 --- a/pkg/types/account.go +++ b/pkg/types/account.go @@ -2,12 +2,7 @@ package types import ( "fmt" - "sort" - "strings" "sync" - "time" - - "github.com/slack-go/slack" "github.com/sirupsen/logrus" "github.com/spf13/viper" @@ -21,253 +16,12 @@ func init() { debugBalance = viper.GetBool("debug-balance") } -type Balance struct { - Currency string `json:"currency"` - Available fixedpoint.Value `json:"available"` - Locked fixedpoint.Value `json:"locked,omitempty"` - - // margin related fields - Borrowed fixedpoint.Value `json:"borrowed,omitempty"` - Interest fixedpoint.Value `json:"interest,omitempty"` - - // NetAsset = (Available + Locked) - Borrowed - Interest - NetAsset fixedpoint.Value `json:"net,omitempty"` -} - -func (b Balance) Total() fixedpoint.Value { - return b.Available.Add(b.Locked) -} - -func (b Balance) String() (o string) { - - o = fmt.Sprintf("%s: %s", b.Currency, b.Available.String()) - - if b.Locked.Sign() > 0 { - o += fmt.Sprintf(" (locked %v)", b.Locked) - } - - if b.Borrowed.Sign() > 0 { - o += fmt.Sprintf(" (borrowed: %v)", b.Borrowed) - } - - return o -} - -type Asset struct { - Currency string `json:"currency" db:"currency"` - Total fixedpoint.Value `json:"total" db:"total"` - InUSD fixedpoint.Value `json:"inUSD" db:"in_usd"` - InBTC fixedpoint.Value `json:"inBTC" db:"in_btc"` - Time time.Time `json:"time" db:"time"` - Locked fixedpoint.Value `json:"lock" db:"lock" ` - Available fixedpoint.Value `json:"available" db:"available"` - Borrowed fixedpoint.Value `json:"borrowed" db:"borrowed"` - NetAsset fixedpoint.Value `json:"netAsset" db:"net_asset"` - PriceInUSD fixedpoint.Value `json:"priceInUSD" db:"price_in_usd"` -} - -type AssetMap map[string]Asset - -func (m AssetMap) PlainText() (o string) { - var assets = m.Slice() - - // sort assets - sort.Slice(assets, func(i, j int) bool { - return assets[i].InUSD.Compare(assets[j].InUSD) > 0 - }) - - sumUsd := fixedpoint.Zero - sumBTC := fixedpoint.Zero - for _, a := range assets { - usd := a.InUSD - btc := a.InBTC - if !a.InUSD.IsZero() { - o += fmt.Sprintf(" %s: %s (≈ %s) (≈ %s)", - a.Currency, - a.Total.String(), - USD.FormatMoney(usd), - BTC.FormatMoney(btc), - ) + "\n" - sumUsd = sumUsd.Add(usd) - sumBTC = sumBTC.Add(btc) - } else { - o += fmt.Sprintf(" %s: %s", - a.Currency, - a.Total.String(), - ) + "\n" - } - } - o += fmt.Sprintf(" Summary: (≈ %s) (≈ %s)", - USD.FormatMoney(sumUsd), - BTC.FormatMoney(sumBTC), - ) + "\n" - return o -} - -func (m AssetMap) Slice() (assets []Asset) { - for _, a := range m { - assets = append(assets, a) - } - return assets -} - -func (m AssetMap) SlackAttachment() slack.Attachment { - var fields []slack.AttachmentField - var totalBTC, totalUSD fixedpoint.Value - - var assets = m.Slice() - - // sort assets - sort.Slice(assets, func(i, j int) bool { - return assets[i].InUSD.Compare(assets[j].InUSD) > 0 - }) - - for _, a := range assets { - totalUSD = totalUSD.Add(a.InUSD) - totalBTC = totalBTC.Add(a.InBTC) - } - - for _, a := range assets { - if !a.InUSD.IsZero() { - fields = append(fields, slack.AttachmentField{ - Title: a.Currency, - Value: fmt.Sprintf("%s (≈ %s) (≈ %s) (%s)", - a.Total.String(), - USD.FormatMoney(a.InUSD), - BTC.FormatMoney(a.InBTC), - a.InUSD.Div(totalUSD).FormatPercentage(2), - ), - Short: false, - }) - } else { - fields = append(fields, slack.AttachmentField{ - Title: a.Currency, - Value: fmt.Sprintf("%s", a.Total.String()), - Short: false, - }) - } - } - - return slack.Attachment{ - Title: fmt.Sprintf("Net Asset Value %s (≈ %s)", - USD.FormatMoney(totalUSD), - BTC.FormatMoney(totalBTC), - ), - Fields: fields, - } -} - -type BalanceMap map[string]Balance type PositionMap map[string]Position type IsolatedMarginAssetMap map[string]IsolatedMarginAsset type MarginAssetMap map[string]MarginUserAsset type FuturesAssetMap map[string]FuturesUserAsset type FuturesPositionMap map[string]FuturesPosition -func (m BalanceMap) Currencies() (currencies []string) { - for _, b := range m { - currencies = append(currencies, b.Currency) - } - return currencies -} - -func (m BalanceMap) Add(bm BalanceMap) BalanceMap { - var total = BalanceMap{} - for _, b := range bm { - tb := total[b.Currency] - tb.Available = tb.Available.Add(b.Available) - tb.Locked = tb.Locked.Add(b.Locked) - tb.Borrowed = tb.Borrowed.Add(b.Borrowed) - tb.NetAsset = tb.NetAsset.Add(b.NetAsset) - tb.Interest = tb.Interest.Add(b.Interest) - total[b.Currency] = tb - } - return total -} - -func (m BalanceMap) String() string { - var ss []string - for _, b := range m { - ss = append(ss, b.String()) - } - - return "BalanceMap[" + strings.Join(ss, ", ") + "]" -} - -func (m BalanceMap) Copy() (d BalanceMap) { - d = make(BalanceMap) - for c, b := range m { - d[c] = b - } - return d -} - -// Assets converts balances into assets with the given prices -func (m BalanceMap) Assets(prices map[string]fixedpoint.Value, priceTime time.Time) AssetMap { - assets := make(AssetMap) - btcusdt, hasBtcPrice := prices["BTCUSDT"] - for currency, b := range m { - if b.Locked.IsZero() && b.Available.IsZero() && b.Borrowed.IsZero() { - continue - } - - total := b.Available.Add(b.Locked) - netAsset := b.NetAsset - if netAsset.IsZero() { - netAsset = total.Sub(b.Borrowed) - } - - asset := Asset{ - Currency: currency, - Total: total, - Time: priceTime, - Locked: b.Locked, - Available: b.Available, - Borrowed: b.Borrowed, - NetAsset: netAsset, - } - - usdMarkets := []string{currency + "USDT", currency + "USDC", currency + "USD", "USDT" + currency} - for _, market := range usdMarkets { - if usdPrice, ok := prices[market] ; ok { - // this includes USDT, USD, USDC and so on - if strings.HasPrefix(market, "USD") { - if !asset.Total.IsZero() { - asset.InUSD = asset.Total.Div(usdPrice) - } - asset.PriceInUSD = usdPrice - } else { - if !asset.Total.IsZero() { - asset.InUSD = asset.Total.Mul(usdPrice) - } - asset.PriceInUSD = fixedpoint.One.Div(usdPrice) - } - - if hasBtcPrice && !asset.InUSD.IsZero() { - asset.InBTC = asset.InUSD.Div(btcusdt) - } - } - assets[currency] = asset - } - } - - return assets -} - -func (m BalanceMap) Print() { - for _, balance := range m { - if balance.Available.IsZero() && balance.Locked.IsZero() { - continue - } - - if balance.Locked.Sign() > 0 { - logrus.Infof(" %s: %v (locked %v)", balance.Currency, balance.Available, balance.Locked) - } else { - logrus.Infof(" %s: %v", balance.Currency, balance.Available) - } - } -} - type AccountType string const ( diff --git a/pkg/types/asset.go b/pkg/types/asset.go new file mode 100644 index 000000000..13c34b5a7 --- /dev/null +++ b/pkg/types/asset.go @@ -0,0 +1,115 @@ +package types + +import ( + "fmt" + "sort" + "time" + + "github.com/slack-go/slack" + + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +type Asset struct { + Currency string `json:"currency" db:"currency"` + Total fixedpoint.Value `json:"total" db:"total"` + InUSD fixedpoint.Value `json:"inUSD" db:"in_usd"` + InBTC fixedpoint.Value `json:"inBTC" db:"in_btc"` + Time time.Time `json:"time" db:"time"` + Locked fixedpoint.Value `json:"lock" db:"lock" ` + Available fixedpoint.Value `json:"available" db:"available"` + Borrowed fixedpoint.Value `json:"borrowed" db:"borrowed"` + NetAsset fixedpoint.Value `json:"netAsset" db:"net_asset"` + PriceInUSD fixedpoint.Value `json:"priceInUSD" db:"price_in_usd"` +} + +type AssetMap map[string]Asset + +func (m AssetMap) PlainText() (o string) { + var assets = m.Slice() + + // sort assets + sort.Slice(assets, func(i, j int) bool { + return assets[i].InUSD.Compare(assets[j].InUSD) > 0 + }) + + sumUsd := fixedpoint.Zero + sumBTC := fixedpoint.Zero + for _, a := range assets { + usd := a.InUSD + btc := a.InBTC + if !a.InUSD.IsZero() { + o += fmt.Sprintf(" %s: %s (≈ %s) (≈ %s)", + a.Currency, + a.Total.String(), + USD.FormatMoney(usd), + BTC.FormatMoney(btc), + ) + "\n" + sumUsd = sumUsd.Add(usd) + sumBTC = sumBTC.Add(btc) + } else { + o += fmt.Sprintf(" %s: %s", + a.Currency, + a.Total.String(), + ) + "\n" + } + } + o += fmt.Sprintf(" Summary: (≈ %s) (≈ %s)", + USD.FormatMoney(sumUsd), + BTC.FormatMoney(sumBTC), + ) + "\n" + return o +} + +func (m AssetMap) Slice() (assets []Asset) { + for _, a := range m { + assets = append(assets, a) + } + return assets +} + +func (m AssetMap) SlackAttachment() slack.Attachment { + var fields []slack.AttachmentField + var totalBTC, totalUSD fixedpoint.Value + + var assets = m.Slice() + + // sort assets + sort.Slice(assets, func(i, j int) bool { + return assets[i].InUSD.Compare(assets[j].InUSD) > 0 + }) + + for _, a := range assets { + totalUSD = totalUSD.Add(a.InUSD) + totalBTC = totalBTC.Add(a.InBTC) + } + + for _, a := range assets { + if !a.InUSD.IsZero() { + fields = append(fields, slack.AttachmentField{ + Title: a.Currency, + Value: fmt.Sprintf("%s (≈ %s) (≈ %s) (%s)", + a.Total.String(), + USD.FormatMoney(a.InUSD), + BTC.FormatMoney(a.InBTC), + a.InUSD.Div(totalUSD).FormatPercentage(2), + ), + Short: false, + }) + } else { + fields = append(fields, slack.AttachmentField{ + Title: a.Currency, + Value: fmt.Sprintf("%s", a.Total.String()), + Short: false, + }) + } + } + + return slack.Attachment{ + Title: fmt.Sprintf("Net Asset Value %s (≈ %s)", + USD.FormatMoney(totalUSD), + BTC.FormatMoney(totalBTC), + ), + Fields: fields, + } +} diff --git a/pkg/types/balance.go b/pkg/types/balance.go new file mode 100644 index 000000000..0fa66b349 --- /dev/null +++ b/pkg/types/balance.go @@ -0,0 +1,150 @@ +package types + +import ( + "fmt" + "strings" + "time" + + "github.com/sirupsen/logrus" + + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +type Balance struct { + Currency string `json:"currency"` + Available fixedpoint.Value `json:"available"` + Locked fixedpoint.Value `json:"locked,omitempty"` + + // margin related fields + Borrowed fixedpoint.Value `json:"borrowed,omitempty"` + Interest fixedpoint.Value `json:"interest,omitempty"` + + // NetAsset = (Available + Locked) - Borrowed - Interest + NetAsset fixedpoint.Value `json:"net,omitempty"` +} + +func (b Balance) Total() fixedpoint.Value { + return b.Available.Add(b.Locked) +} + +func (b Balance) String() (o string) { + o = fmt.Sprintf("%s: %s", b.Currency, b.Available.String()) + + if b.Locked.Sign() > 0 { + o += fmt.Sprintf(" (locked %v)", b.Locked) + } + + if b.Borrowed.Sign() > 0 { + o += fmt.Sprintf(" (borrowed: %v)", b.Borrowed) + } + + return o +} + + +type BalanceMap map[string]Balance + +func (m BalanceMap) Currencies() (currencies []string) { + for _, b := range m { + currencies = append(currencies, b.Currency) + } + return currencies +} + +func (m BalanceMap) Add(bm BalanceMap) BalanceMap { + var total = BalanceMap{} + for _, b := range bm { + tb := total[b.Currency] + tb.Available = tb.Available.Add(b.Available) + tb.Locked = tb.Locked.Add(b.Locked) + tb.Borrowed = tb.Borrowed.Add(b.Borrowed) + tb.NetAsset = tb.NetAsset.Add(b.NetAsset) + tb.Interest = tb.Interest.Add(b.Interest) + total[b.Currency] = tb + } + return total +} + +func (m BalanceMap) String() string { + var ss []string + for _, b := range m { + ss = append(ss, b.String()) + } + + return "BalanceMap[" + strings.Join(ss, ", ") + "]" +} + +func (m BalanceMap) Copy() (d BalanceMap) { + d = make(BalanceMap) + for c, b := range m { + d[c] = b + } + return d +} + +// Assets converts balances into assets with the given prices +func (m BalanceMap) Assets(prices map[string]fixedpoint.Value, priceTime time.Time) AssetMap { + assets := make(AssetMap) + btcusdt, hasBtcPrice := prices["BTCUSDT"] + for currency, b := range m { + if b.Locked.IsZero() && b.Available.IsZero() && b.Borrowed.IsZero() { + continue + } + + total := b.Available.Add(b.Locked) + netAsset := b.NetAsset + if netAsset.IsZero() { + netAsset = total.Sub(b.Borrowed) + } + + asset := Asset{ + Currency: currency, + Total: total, + Time: priceTime, + Locked: b.Locked, + Available: b.Available, + Borrowed: b.Borrowed, + NetAsset: netAsset, + } + + usdMarkets := []string{currency + "USDT", currency + "USDC", currency + "USD", "USDT" + currency} + for _, market := range usdMarkets { + if usdPrice, ok := prices[market]; ok { + // this includes USDT, USD, USDC and so on + if strings.HasPrefix(market, "USD") { + if !asset.Total.IsZero() { + asset.InUSD = asset.Total.Div(usdPrice) + } + asset.PriceInUSD = usdPrice + } else { + if !asset.Total.IsZero() { + asset.InUSD = asset.Total.Mul(usdPrice) + } + asset.PriceInUSD = fixedpoint.One.Div(usdPrice) + } + + if hasBtcPrice && !asset.InUSD.IsZero() { + asset.InBTC = asset.InUSD.Div(btcusdt) + } + } + assets[currency] = asset + } + } + + return assets +} + +func (m BalanceMap) Print() { + for _, balance := range m { + if balance.Available.IsZero() && balance.Locked.IsZero() { + continue + } + + if balance.Locked.Sign() > 0 { + logrus.Infof(" %s: %v (locked %v)", balance.Currency, balance.Available, balance.Locked) + } else { + logrus.Infof(" %s: %v", balance.Currency, balance.Available) + } + } +} +