refactor balance, asset and remove price cache check

This commit is contained in:
c9s 2022-05-04 17:17:09 +08:00
parent f33e8a3de2
commit 36c764efa9
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
5 changed files with 277 additions and 257 deletions

View File

@ -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) { func (session *ExchangeSession) UpdatePrices(ctx context.Context, currencies []string, fiat string) (err error) {
if session.lastPriceUpdatedAt.After(time.Now().Add(-time.Hour)) { // TODO: move this cache check to the http routes
return nil // if session.lastPriceUpdatedAt.After(time.Now().Add(-time.Hour)) {
} // return nil
// }
var symbols []string var symbols []string
for _, c := range currencies { for _, c := range currencies {

View File

@ -78,7 +78,6 @@ var Ten = fixedpoint.NewFromInt(10)
func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {} func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {}
func (s *Strategy) recordNetAssetValue(ctx context.Context, sessions map[string]*bbgo.ExchangeSession) { func (s *Strategy) recordNetAssetValue(ctx context.Context, sessions map[string]*bbgo.ExchangeSession) {
totalAssets := types.AssetMap{}
totalBalances := types.BalanceMap{} totalBalances := types.BalanceMap{}
allPrices := map[string]fixedpoint.Value{} allPrices := map[string]fixedpoint.Value{}
sessionBalances := map[string]types.BalanceMap{} 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) s.Environment.RecordAsset(priceTime, session, assets)
} }
allAssets := totalBalances.Assets(allPrices, priceTime) displayAssets := types.AssetMap{}
for currency, asset := range allAssets { 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. // calculated if it's dust only when InUSD (usd value) is defined.
if s.IgnoreDusts && !asset.InUSD.IsZero() && asset.InUSD.Compare(Ten) < 0 { if s.IgnoreDusts && !asset.InUSD.IsZero() && asset.InUSD.Compare(Ten) < 0 {
continue continue
} }
totalAssets[currency] = asset displayAssets[currency] = asset
} }
s.Environment.RecordAsset(priceTime, &bbgo.ExchangeSession{Name: "ALL"}, totalAssets) s.Notifiability.Notify(displayAssets)
s.Notifiability.Notify(totalAssets)
if s.state != nil { if s.state != nil {
if s.state.IsOver24Hours() { 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 // 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 {
} }

View File

@ -2,12 +2,7 @@ package types
import ( import (
"fmt" "fmt"
"sort"
"strings"
"sync" "sync"
"time"
"github.com/slack-go/slack"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -21,253 +16,12 @@ func init() {
debugBalance = viper.GetBool("debug-balance") 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 PositionMap map[string]Position
type IsolatedMarginAssetMap map[string]IsolatedMarginAsset type IsolatedMarginAssetMap map[string]IsolatedMarginAsset
type MarginAssetMap map[string]MarginUserAsset type MarginAssetMap map[string]MarginUserAsset
type FuturesAssetMap map[string]FuturesUserAsset type FuturesAssetMap map[string]FuturesUserAsset
type FuturesPositionMap map[string]FuturesPosition 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 type AccountType string
const ( const (

115
pkg/types/asset.go Normal file
View File

@ -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,
}
}

150
pkg/types/balance.go Normal file
View File

@ -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)
}
}
}