From f0ea9a357ac19ef788ea95e9d8f9f277add6a33a Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 13 Sep 2022 13:36:07 +0800 Subject: [PATCH 1/8] pivotshort: add one more kline price compare condition --- pkg/strategy/pivotshort/failedbreakhigh.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/strategy/pivotshort/failedbreakhigh.go b/pkg/strategy/pivotshort/failedbreakhigh.go index e87aaba3a..07a81ea5c 100644 --- a/pkg/strategy/pivotshort/failedbreakhigh.go +++ b/pkg/strategy/pivotshort/failedbreakhigh.go @@ -150,8 +150,9 @@ func (s *FailedBreakHigh) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg } // the kline opened below the last break low, and closed above the last break low - if k.Open.Compare(s.lastFailedBreakHigh) < 0 && k.Close.Compare(s.lastFailedBreakHigh) > 0 { + if k.Open.Compare(s.lastFailedBreakHigh) < 0 && k.Close.Compare(s.lastFailedBreakHigh) > 0 && k.Open.Compare(k.Close) > 0 { bbgo.Notify("kLine closed above the last break high, triggering stop earlier") + if err := s.orderExecutor.ClosePosition(context.Background(), one, "failedBreakHighStop"); err != nil { log.WithError(err).Error("position close error") } From fe9a546c65fc19d1d1db9d759e324ec2ddce2fba Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 13 Sep 2022 13:57:49 +0800 Subject: [PATCH 2/8] pivotshort: improve notification --- pkg/strategy/pivotshort/failedbreakhigh.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/strategy/pivotshort/failedbreakhigh.go b/pkg/strategy/pivotshort/failedbreakhigh.go index 07a81ea5c..18c7c03e4 100644 --- a/pkg/strategy/pivotshort/failedbreakhigh.go +++ b/pkg/strategy/pivotshort/failedbreakhigh.go @@ -183,13 +183,13 @@ func (s *FailedBreakHigh) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg // we need few conditions: // 1) kline.High is higher than the previous high // 2) kline.Close is lower than the previous high - // 3) kline.Close is lower than kline.Open if kline.High.Compare(breakPrice) < 0 || closePrice.Compare(breakPrice) >= 0 { return } + // 3) kline.Close is lower than kline.Open if closePrice.Compare(openPrice) > 0 { - bbgo.Notify("the closed price is higher than the open price, skip failed break high short") + bbgo.Notify("the %s closed price %f is higher than the open price %f, skip failed break high short", s.Symbol, closePrice.Float64(), openPrice.Float64()) return } From 761767965187a6b18ab28f9564fba7507ad00f9f Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 13 Sep 2022 18:43:03 +0800 Subject: [PATCH 3/8] pivotshort: add EarlyStopRatio config --- pkg/strategy/pivotshort/failedbreakhigh.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pkg/strategy/pivotshort/failedbreakhigh.go b/pkg/strategy/pivotshort/failedbreakhigh.go index 18c7c03e4..dde5b2a46 100644 --- a/pkg/strategy/pivotshort/failedbreakhigh.go +++ b/pkg/strategy/pivotshort/failedbreakhigh.go @@ -29,6 +29,10 @@ type FailedBreakHigh struct { // Ratio is a number less than 1.0, price * ratio will be the price triggers the short order. Ratio fixedpoint.Value `json:"ratio"` + // EarlyStopRatio adjusts the break high price with the given ratio + // this is for stop loss earlier if the price goes above the previous price + EarlyStopRatio fixedpoint.Value `json:"earlyStopRatio"` + VWMA *types.IntervalWindow `json:"vwma"` StopEMA *bbgo.StopEMA `json:"stopEMA"` @@ -123,7 +127,7 @@ func (s *FailedBreakHigh) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg return } - bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivotHigh.Last()) + bbgo.Notify("%s new pivot high: %f", s.Symbol, s.pivotHigh.Last()) } })) @@ -149,9 +153,15 @@ func (s *FailedBreakHigh) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg return } + lastHigh := s.lastFastHigh + + if !s.EarlyStopRatio.IsZero() { + lastHigh = lastHigh.Mul(one.Add(s.EarlyStopRatio)) + } + // the kline opened below the last break low, and closed above the last break low - if k.Open.Compare(s.lastFailedBreakHigh) < 0 && k.Close.Compare(s.lastFailedBreakHigh) > 0 && k.Open.Compare(k.Close) > 0 { - bbgo.Notify("kLine closed above the last break high, triggering stop earlier") + if k.Open.Compare(lastHigh) < 0 && k.Close.Compare(lastHigh) > 0 && k.Open.Compare(k.Close) > 0 { + bbgo.Notify("kLine closed %f above the last break high %f (ratio %f), triggering stop earlier", k.Close.Float64(), lastHigh.Float64(), s.EarlyStopRatio.Float64()) if err := s.orderExecutor.ClosePosition(context.Background(), one, "failedBreakHighStop"); err != nil { log.WithError(err).Error("position close error") From 809294b05467a61af1c34bb4b5d566b00aa9a836 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 14 Sep 2022 02:08:14 +0800 Subject: [PATCH 4/8] bbgo: add test case for calculateNetValueInQuote --- pkg/bbgo/risk.go | 31 +++++++++-------- pkg/bbgo/risk_test.go | 77 ++++++++++++++++++++++++++++++++++++++++++- pkg/types/balance.go | 8 +++-- 3 files changed, 100 insertions(+), 16 deletions(-) diff --git a/pkg/bbgo/risk.go b/pkg/bbgo/risk.go index 51b0ce7bb..aaa74ad01 100644 --- a/pkg/bbgo/risk.go +++ b/pkg/bbgo/risk.go @@ -115,32 +115,37 @@ func (c *AccountValueCalculator) MarketValue(ctx context.Context) (fixedpoint.Va } func (c *AccountValueCalculator) NetValue(ctx context.Context) (fixedpoint.Value, error) { - accountValue := fixedpoint.Zero - if len(c.prices) == 0 { if err := c.UpdatePrices(ctx); err != nil { - return accountValue, err + return fixedpoint.Zero, err } } balances := c.session.Account.Balances() + accountValue := calculateNetValueInQuote(balances, c.prices, c.quoteCurrency) + return accountValue, nil +} + +func calculateNetValueInQuote(balances types.BalanceMap, prices map[string]fixedpoint.Value, quoteCurrency string) (accountValue fixedpoint.Value) { + accountValue = fixedpoint.Zero + for _, b := range balances { - if b.Currency == c.quoteCurrency { + if b.Currency == quoteCurrency { accountValue = accountValue.Add(b.Net()) continue } - symbol := b.Currency + c.quoteCurrency // for BTC/USDT, ETH/USDT pairs - symbolReverse := c.quoteCurrency + b.Currency // for USDT/USDC or USDT/TWD pairs - if price, ok := c.prices[symbol]; ok { + symbol := b.Currency + quoteCurrency // for BTC/USDT, ETH/USDT pairs + symbolReverse := quoteCurrency + b.Currency // for USDT/USDC or USDT/TWD pairs + if price, ok := prices[symbol]; ok { accountValue = accountValue.Add(b.Net().Mul(price)) - } else if priceReverse, ok2 := c.prices[symbolReverse]; ok2 { + } else if priceReverse, ok2 := prices[symbolReverse]; ok2 { price2 := one.Div(priceReverse) accountValue = accountValue.Add(b.Net().Mul(price2)) } } - return accountValue, nil + return accountValue } func (c *AccountValueCalculator) AvailableQuote(ctx context.Context) (fixedpoint.Value, error) { @@ -189,7 +194,7 @@ func (c *AccountValueCalculator) MarginLevel(ctx context.Context) (fixedpoint.Va return marginLevel, nil } -func aggregateUsdValue(balances types.BalanceMap) fixedpoint.Value { +func aggregateUsdNetValue(balances types.BalanceMap) fixedpoint.Value { totalUsdValue := fixedpoint.Zero // get all usd value if any for currency, balance := range balances { @@ -247,7 +252,7 @@ func CalculateBaseQuantity(session *ExchangeSession, market types.Market, price, // for isolated margin we can calculate from these two pair totalUsdValue := fixedpoint.Zero if len(restBalances) == 1 && types.IsUSDFiatCurrency(market.QuoteCurrency) { - totalUsdValue = aggregateUsdValue(balances) + totalUsdValue = aggregateUsdNetValue(balances) } else if len(restBalances) > 1 { accountValue := NewAccountValueCalculator(session, "USDT") netValue, err := accountValue.NetValue(context.Background()) @@ -258,7 +263,7 @@ func CalculateBaseQuantity(session *ExchangeSession, market types.Market, price, totalUsdValue = netValue } else { // TODO: translate quote currency like BTC of ETH/BTC to usd value - totalUsdValue = aggregateUsdValue(usdBalances) + totalUsdValue = aggregateUsdNetValue(usdBalances) } if !quantity.IsZero() { @@ -270,7 +275,7 @@ func CalculateBaseQuantity(session *ExchangeSession, market types.Market, price, } // using leverage -- starts from here - log.Infof("calculating available leveraged base quantity: base balance = %+v, quote balance = %+v", baseBalance, quoteBalance) + log.Infof("calculating available leveraged base quantity: base balance = %+v, total usd value %f", baseBalance, totalUsdValue.Float64()) // calculate the quantity automatically if session.Margin || session.IsolatedMargin { diff --git a/pkg/bbgo/risk_test.go b/pkg/bbgo/risk_test.go index 9c486af32..6dd71a150 100644 --- a/pkg/bbgo/risk_test.go +++ b/pkg/bbgo/risk_test.go @@ -184,7 +184,7 @@ func Test_aggregateUsdValue(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, aggregateUsdValue(tt.args.balances), "aggregateUsdValue(%v)", tt.args.balances) + assert.Equalf(t, tt.want, aggregateUsdNetValue(tt.args.balances), "aggregateUsdNetValue(%v)", tt.args.balances) }) } } @@ -226,3 +226,78 @@ func Test_usdFiatBalances(t *testing.T) { }) } } + +func Test_calculateNetValueInQuote(t *testing.T) { + type args struct { + balances types.BalanceMap + prices map[string]fixedpoint.Value + quoteCurrency string + } + tests := []struct { + name string + args args + wantAccountValue fixedpoint.Value + }{ + { + name: "positive asset", + args: args{ + balances: types.BalanceMap{ + "USDC": types.Balance{Currency: "USDC", Available: number(70.0)}, + "USDT": types.Balance{Currency: "USDT", Available: number(100.0)}, + "BUSD": types.Balance{Currency: "BUSD", Available: number(80.0)}, + "BTC": types.Balance{Currency: "BTC", Available: number(0.01)}, + }, + prices: map[string]fixedpoint.Value{ + "USDCUSDT": number(1.0), + "BUSDUSDT": number(1.0), + "BTCUSDT": number(19000.0), + }, + quoteCurrency: "USDT", + }, + wantAccountValue: number(19000.0*0.01 + 100.0 + 80.0 + 70.0), + }, + { + name: "borrow base asset", + args: args{ + balances: types.BalanceMap{ + "USDT": types.Balance{Currency: "USDT", Available: number(20000.0 * 2)}, + "USDC": types.Balance{Currency: "USDC", Available: number(70.0)}, + "BUSD": types.Balance{Currency: "BUSD", Available: number(80.0)}, + "BTC": types.Balance{Currency: "BTC", Available: number(0), Borrowed: number(2.0)}, + }, + prices: map[string]fixedpoint.Value{ + "USDCUSDT": number(1.0), + "BUSDUSDT": number(1.0), + "BTCUSDT": number(19000.0), + }, + quoteCurrency: "USDT", + }, + wantAccountValue: number(19000.0*-2.0 + 20000.0*2 + 80.0 + 70.0), + }, + { + name: "multi base asset", + args: args{ + balances: types.BalanceMap{ + "USDT": types.Balance{Currency: "USDT", Available: number(20000.0 * 2)}, + "USDC": types.Balance{Currency: "USDC", Available: number(70.0)}, + "BUSD": types.Balance{Currency: "BUSD", Available: number(80.0)}, + "ETH": types.Balance{Currency: "ETH", Available: number(10.0)}, + "BTC": types.Balance{Currency: "BTC", Available: number(0), Borrowed: number(2.0)}, + }, + prices: map[string]fixedpoint.Value{ + "USDCUSDT": number(1.0), + "BUSDUSDT": number(1.0), + "ETHUSDT": number(1700.0), + "BTCUSDT": number(19000.0), + }, + quoteCurrency: "USDT", + }, + wantAccountValue: number(19000.0*-2.0 + 1700.0*10.0 + 20000.0*2 + 80.0 + 70.0), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.wantAccountValue, calculateNetValueInQuote(tt.args.balances, tt.args.prices, tt.args.quoteCurrency), "calculateNetValueInQuote(%v, %v, %v)", tt.args.balances, tt.args.prices, tt.args.quoteCurrency) + }) + } +} diff --git a/pkg/types/balance.go b/pkg/types/balance.go index 3dea8ad64..cbcb48e53 100644 --- a/pkg/types/balance.go +++ b/pkg/types/balance.go @@ -50,7 +50,7 @@ func (b Balance) Debt() fixedpoint.Value { } func (b Balance) ValueString() (o string) { - o = b.Available.String() + o = b.Net().String() if b.Locked.Sign() > 0 { o += fmt.Sprintf(" (locked %v)", b.Locked) @@ -64,7 +64,7 @@ func (b Balance) ValueString() (o string) { } func (b Balance) String() (o string) { - o = fmt.Sprintf("%s: %s", b.Currency, b.Available.String()) + o = fmt.Sprintf("%s: %s", b.Currency, b.Net().String()) if b.Locked.Sign() > 0 { o += fmt.Sprintf(" (locked %v)", b.Locked) @@ -74,6 +74,10 @@ func (b Balance) String() (o string) { o += fmt.Sprintf(" (borrowed: %v)", b.Borrowed) } + if b.Interest.Sign() > 0 { + o += fmt.Sprintf(" (interest: %v)", b.Interest) + } + return o } From c9b064f0ac5eeb88bd0e9dd5c384b8373e4fe89b Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 14 Sep 2022 02:15:04 +0800 Subject: [PATCH 5/8] types: define PriceMap type --- pkg/bbgo/risk.go | 2 +- pkg/bbgo/risk_test.go | 8 ++++---- pkg/types/balance.go | 8 +++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/bbgo/risk.go b/pkg/bbgo/risk.go index aaa74ad01..abc49ebee 100644 --- a/pkg/bbgo/risk.go +++ b/pkg/bbgo/risk.go @@ -126,7 +126,7 @@ func (c *AccountValueCalculator) NetValue(ctx context.Context) (fixedpoint.Value return accountValue, nil } -func calculateNetValueInQuote(balances types.BalanceMap, prices map[string]fixedpoint.Value, quoteCurrency string) (accountValue fixedpoint.Value) { +func calculateNetValueInQuote(balances types.BalanceMap, prices types.PriceMap, quoteCurrency string) (accountValue fixedpoint.Value) { accountValue = fixedpoint.Zero for _, b := range balances { diff --git a/pkg/bbgo/risk_test.go b/pkg/bbgo/risk_test.go index 6dd71a150..1382b4adf 100644 --- a/pkg/bbgo/risk_test.go +++ b/pkg/bbgo/risk_test.go @@ -230,7 +230,7 @@ func Test_usdFiatBalances(t *testing.T) { func Test_calculateNetValueInQuote(t *testing.T) { type args struct { balances types.BalanceMap - prices map[string]fixedpoint.Value + prices types.PriceMap quoteCurrency string } tests := []struct { @@ -247,7 +247,7 @@ func Test_calculateNetValueInQuote(t *testing.T) { "BUSD": types.Balance{Currency: "BUSD", Available: number(80.0)}, "BTC": types.Balance{Currency: "BTC", Available: number(0.01)}, }, - prices: map[string]fixedpoint.Value{ + prices: types.PriceMap{ "USDCUSDT": number(1.0), "BUSDUSDT": number(1.0), "BTCUSDT": number(19000.0), @@ -265,7 +265,7 @@ func Test_calculateNetValueInQuote(t *testing.T) { "BUSD": types.Balance{Currency: "BUSD", Available: number(80.0)}, "BTC": types.Balance{Currency: "BTC", Available: number(0), Borrowed: number(2.0)}, }, - prices: map[string]fixedpoint.Value{ + prices: types.PriceMap{ "USDCUSDT": number(1.0), "BUSDUSDT": number(1.0), "BTCUSDT": number(19000.0), @@ -284,7 +284,7 @@ func Test_calculateNetValueInQuote(t *testing.T) { "ETH": types.Balance{Currency: "ETH", Available: number(10.0)}, "BTC": types.Balance{Currency: "BTC", Available: number(0), Borrowed: number(2.0)}, }, - prices: map[string]fixedpoint.Value{ + prices: types.PriceMap{ "USDCUSDT": number(1.0), "BUSDUSDT": number(1.0), "ETHUSDT": number(1700.0), diff --git a/pkg/types/balance.go b/pkg/types/balance.go index cbcb48e53..a73ecc3e4 100644 --- a/pkg/types/balance.go +++ b/pkg/types/balance.go @@ -12,6 +12,8 @@ import ( "github.com/c9s/bbgo/pkg/fixedpoint" ) +type PriceMap map[string]fixedpoint.Value + type Balance struct { Currency string `json:"currency"` Available fixedpoint.Value `json:"available"` @@ -159,7 +161,7 @@ func (m BalanceMap) Copy() (d BalanceMap) { } // Assets converts balances into assets with the given prices -func (m BalanceMap) Assets(prices map[string]fixedpoint.Value, priceTime time.Time) AssetMap { +func (m BalanceMap) Assets(prices PriceMap, priceTime time.Time) AssetMap { assets := make(AssetMap) _, btcInUSD, hasBtcPrice := findUSDMarketPrice("BTC", prices) @@ -182,7 +184,7 @@ func (m BalanceMap) Assets(prices map[string]fixedpoint.Value, priceTime time.Ti NetAsset: netAsset, } - if strings.HasPrefix(currency, "USD") { // for usd + if IsUSDFiatCurrency(currency) { // for usd asset.InUSD = netAsset asset.PriceInUSD = fixedpoint.One if hasBtcPrice && !asset.InUSD.IsZero() { @@ -191,7 +193,7 @@ func (m BalanceMap) Assets(prices map[string]fixedpoint.Value, priceTime time.Ti } else { // for crypto if market, usdPrice, ok := findUSDMarketPrice(currency, prices); ok { // this includes USDT, USD, USDC and so on - if strings.HasPrefix(market, "USD") { // for prices like USDT/TWD + if strings.HasPrefix(market, "USD") || strings.HasPrefix(market, "BUSD") { // for prices like USDT/TWD, BUSD/USDT if !asset.NetAsset.IsZero() { asset.InUSD = asset.NetAsset.Div(usdPrice) } From 02dab542c47d063a6b5e660162c2ca7ca4214921 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 14 Sep 2022 02:17:21 +0800 Subject: [PATCH 6/8] bbgo: add USDTTWD price test case --- pkg/bbgo/risk.go | 3 +-- pkg/bbgo/risk_test.go | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pkg/bbgo/risk.go b/pkg/bbgo/risk.go index abc49ebee..633ae42e2 100644 --- a/pkg/bbgo/risk.go +++ b/pkg/bbgo/risk.go @@ -140,8 +140,7 @@ func calculateNetValueInQuote(balances types.BalanceMap, prices types.PriceMap, if price, ok := prices[symbol]; ok { accountValue = accountValue.Add(b.Net().Mul(price)) } else if priceReverse, ok2 := prices[symbolReverse]; ok2 { - price2 := one.Div(priceReverse) - accountValue = accountValue.Add(b.Net().Mul(price2)) + accountValue = accountValue.Add(b.Net().Div(priceReverse)) } } diff --git a/pkg/bbgo/risk_test.go b/pkg/bbgo/risk_test.go index 1382b4adf..f33f5845d 100644 --- a/pkg/bbgo/risk_test.go +++ b/pkg/bbgo/risk_test.go @@ -256,6 +256,26 @@ func Test_calculateNetValueInQuote(t *testing.T) { }, wantAccountValue: number(19000.0*0.01 + 100.0 + 80.0 + 70.0), }, + { + name: "reversed usdt price", + args: args{ + balances: types.BalanceMap{ + "USDC": types.Balance{Currency: "USDC", Available: number(70.0)}, + "TWD": types.Balance{Currency: "TWD", Available: number(3000.0)}, + "USDT": types.Balance{Currency: "USDT", Available: number(100.0)}, + "BUSD": types.Balance{Currency: "BUSD", Available: number(80.0)}, + "BTC": types.Balance{Currency: "BTC", Available: number(0.01)}, + }, + prices: types.PriceMap{ + "USDTTWD": number(30.0), + "USDCUSDT": number(1.0), + "BUSDUSDT": number(1.0), + "BTCUSDT": number(19000.0), + }, + quoteCurrency: "USDT", + }, + wantAccountValue: number(19000.0*0.01 + 100.0 + 80.0 + 70.0 + (3000.0 / 30.0)), + }, { name: "borrow base asset", args: args{ From b4990f173d77427abe3c42db58503d79799d345d Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 14 Sep 2022 02:20:54 +0800 Subject: [PATCH 7/8] xnav: fix negative usd value check --- pkg/strategy/xnav/strategy.go | 2 +- pkg/types/balance.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/strategy/xnav/strategy.go b/pkg/strategy/xnav/strategy.go index 28381a7d6..daceb344a 100644 --- a/pkg/strategy/xnav/strategy.go +++ b/pkg/strategy/xnav/strategy.go @@ -117,7 +117,7 @@ func (s *Strategy) recordNetAssetValue(ctx context.Context, sessions map[string] 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 { + if s.IgnoreDusts && !asset.InUSD.IsZero() && asset.InUSD.Compare(Ten) < 0 && asset.InUSD.Compare(Ten.Neg()) > 0 { continue } diff --git a/pkg/types/balance.go b/pkg/types/balance.go index a73ecc3e4..6d0dce1a6 100644 --- a/pkg/types/balance.go +++ b/pkg/types/balance.go @@ -169,6 +169,7 @@ func (m BalanceMap) Assets(prices PriceMap, priceTime time.Time) AssetMap { for currency, b := range m { total := b.Total() netAsset := b.Net() + if total.IsZero() && netAsset.IsZero() { continue } From b76d77990276cbe6f2be03ae3638b13de4d5914f Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 14 Sep 2022 02:26:06 +0800 Subject: [PATCH 8/8] types: add BalanceMap_Assets test case --- pkg/types/balance_test.go | 59 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/pkg/types/balance_test.go b/pkg/types/balance_test.go index 6b0348192..d3b69aedd 100644 --- a/pkg/types/balance_test.go +++ b/pkg/types/balance_test.go @@ -2,6 +2,7 @@ package types import ( "testing" + "time" "github.com/stretchr/testify/assert" @@ -37,3 +38,61 @@ func TestBalanceMap_Add(t *testing.T) { assert.Len(t, bm3, 2) assert.Equal(t, fixedpoint.MustNewFromString("11.0"), bm3["BTC"].Available) } + +func TestBalanceMap_Assets(t *testing.T) { + type args struct { + prices PriceMap + priceTime time.Time + } + tests := []struct { + name string + m BalanceMap + args args + want AssetMap + }{ + { + m: BalanceMap{ + "USDT": Balance{Currency: "USDT", Available: number(100.0)}, + "BTC": Balance{Currency: "BTC", Borrowed: number(2.0)}, + }, + args: args{ + prices: PriceMap{ + "BTCUSDT": number(19000.0), + }, + }, + want: AssetMap{ + "USDT": { + Currency: "USDT", + Total: number(100), + NetAsset: number(100.0), + Interest: number(0), + InUSD: number(100.0), + InBTC: number(100.0 / 19000.0), + Time: time.Time{}, + Locked: number(0), + Available: number(100.0), + Borrowed: number(0), + PriceInUSD: number(1.0), + }, + "BTC": { + Currency: "BTC", + Total: number(0), + NetAsset: number(-2), + Interest: number(0), + InUSD: number(-2 * 19000.0), + InBTC: number(-2), + Time: time.Time{}, + Locked: number(0), + Available: number(0), + Borrowed: number(2), + PriceInUSD: number(19000.0), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, tt.m.Assets(tt.args.prices, tt.args.priceTime), "Assets(%v, %v)", tt.args.prices, tt.args.priceTime) + }) + } +}