2022-09-09 09:40:17 +00:00
|
|
|
package bbgo
|
2022-07-22 06:42:30 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
2024-03-12 05:09:59 +00:00
|
|
|
"go.uber.org/mock/gomock"
|
2022-07-22 06:42:30 +00:00
|
|
|
|
|
|
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
2024-10-04 11:45:07 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/pricesolver"
|
|
|
|
. "github.com/c9s/bbgo/pkg/testing/testhelper"
|
2022-07-22 06:42:30 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
"github.com/c9s/bbgo/pkg/types/mocks"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestAccountValueCalculator_NetValue(t *testing.T) {
|
2024-10-04 11:45:07 +00:00
|
|
|
symbol := "BTCUSDT"
|
|
|
|
markets := AllMarkets()
|
2022-07-22 06:42:30 +00:00
|
|
|
|
|
|
|
t.Run("borrow and available", func(t *testing.T) {
|
|
|
|
mockCtrl := gomock.NewController(t)
|
|
|
|
defer mockCtrl.Finish()
|
|
|
|
|
2024-10-05 05:09:31 +00:00
|
|
|
ticker := Ticker(symbol)
|
2022-07-22 06:42:30 +00:00
|
|
|
mockEx := mocks.NewMockExchange(mockCtrl)
|
|
|
|
// for market data stream and user data stream
|
|
|
|
mockEx.EXPECT().NewStream().Return(&types.StandardStream{}).Times(2)
|
2024-10-05 05:09:31 +00:00
|
|
|
mockEx.EXPECT().QueryTicker(gomock.Any(), symbol).Return(&ticker, nil).AnyTimes()
|
2022-07-22 06:42:30 +00:00
|
|
|
|
2022-09-09 09:40:17 +00:00
|
|
|
session := NewExchangeSession("test", mockEx)
|
2022-07-22 06:42:30 +00:00
|
|
|
session.Account.UpdateBalances(types.BalanceMap{
|
|
|
|
"BTC": {
|
|
|
|
Currency: "BTC",
|
|
|
|
Available: fixedpoint.NewFromFloat(2.0),
|
|
|
|
Locked: fixedpoint.Zero,
|
|
|
|
Borrowed: fixedpoint.NewFromFloat(1.0),
|
|
|
|
Interest: fixedpoint.Zero,
|
|
|
|
NetAsset: fixedpoint.Zero,
|
|
|
|
},
|
|
|
|
"USDT": {
|
|
|
|
Currency: "USDT",
|
|
|
|
Available: fixedpoint.NewFromFloat(1000.0),
|
|
|
|
Locked: fixedpoint.Zero,
|
|
|
|
Borrowed: fixedpoint.Zero,
|
|
|
|
Interest: fixedpoint.Zero,
|
|
|
|
NetAsset: fixedpoint.Zero,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
assert.NotNil(t, session)
|
|
|
|
|
2024-10-04 11:45:07 +00:00
|
|
|
priceSolver := pricesolver.NewSimplePriceResolver(markets)
|
2024-10-05 05:09:31 +00:00
|
|
|
priceSolver.Update(symbol, ticker.GetValidPrice())
|
2024-10-04 11:45:07 +00:00
|
|
|
|
|
|
|
cal := NewAccountValueCalculator(session, priceSolver, "USDT")
|
2022-07-22 06:42:30 +00:00
|
|
|
|
2024-10-05 05:09:31 +00:00
|
|
|
netValue := cal.NetValue()
|
2022-07-22 06:42:30 +00:00
|
|
|
assert.Equal(t, "20000", netValue.String())
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("borrowed and sold", func(t *testing.T) {
|
|
|
|
mockCtrl := gomock.NewController(t)
|
|
|
|
defer mockCtrl.Finish()
|
|
|
|
|
2024-10-05 05:09:31 +00:00
|
|
|
ticker := Ticker(symbol)
|
|
|
|
|
2022-07-22 06:42:30 +00:00
|
|
|
mockEx := mocks.NewMockExchange(mockCtrl)
|
|
|
|
// for market data stream and user data stream
|
|
|
|
mockEx.EXPECT().NewStream().Return(&types.StandardStream{}).Times(2)
|
2024-10-05 05:09:31 +00:00
|
|
|
mockEx.EXPECT().QueryTicker(gomock.Any(), symbol).Return(&ticker, nil).AnyTimes()
|
2022-07-22 06:42:30 +00:00
|
|
|
|
2022-09-09 09:40:17 +00:00
|
|
|
session := NewExchangeSession("test", mockEx)
|
2022-07-22 06:42:30 +00:00
|
|
|
session.Account.UpdateBalances(types.BalanceMap{
|
|
|
|
"BTC": {
|
|
|
|
Currency: "BTC",
|
|
|
|
Available: fixedpoint.Zero,
|
|
|
|
Locked: fixedpoint.Zero,
|
|
|
|
Borrowed: fixedpoint.NewFromFloat(1.0),
|
|
|
|
Interest: fixedpoint.Zero,
|
|
|
|
NetAsset: fixedpoint.Zero,
|
|
|
|
},
|
|
|
|
"USDT": {
|
|
|
|
Currency: "USDT",
|
|
|
|
Available: fixedpoint.NewFromFloat(21000.0),
|
|
|
|
Locked: fixedpoint.Zero,
|
|
|
|
Borrowed: fixedpoint.Zero,
|
|
|
|
Interest: fixedpoint.Zero,
|
|
|
|
NetAsset: fixedpoint.Zero,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2024-10-04 11:45:07 +00:00
|
|
|
priceSolver := pricesolver.NewSimplePriceResolver(markets)
|
2024-10-05 05:09:31 +00:00
|
|
|
priceSolver.Update(symbol, ticker.GetValidPrice())
|
2024-10-04 11:45:07 +00:00
|
|
|
|
|
|
|
cal := NewAccountValueCalculator(session, priceSolver, "USDT")
|
2024-10-05 05:09:31 +00:00
|
|
|
netValue := cal.NetValue()
|
2022-07-22 06:42:30 +00:00
|
|
|
assert.Equal(t, "2000", netValue.String()) // 21000-19000
|
|
|
|
})
|
|
|
|
}
|
2022-07-22 06:53:17 +00:00
|
|
|
|
|
|
|
func TestNewAccountValueCalculator_MarginLevel(t *testing.T) {
|
|
|
|
mockCtrl := gomock.NewController(t)
|
|
|
|
defer mockCtrl.Finish()
|
|
|
|
|
2024-10-05 05:09:31 +00:00
|
|
|
symbol := "BTCUSDT"
|
|
|
|
ticker := Ticker(symbol)
|
2024-10-04 11:45:07 +00:00
|
|
|
|
2022-07-22 06:53:17 +00:00
|
|
|
mockEx := mocks.NewMockExchange(mockCtrl)
|
|
|
|
// for market data stream and user data stream
|
|
|
|
mockEx.EXPECT().NewStream().Return(&types.StandardStream{}).Times(2)
|
2024-10-05 05:09:31 +00:00
|
|
|
mockEx.EXPECT().QueryTicker(gomock.Any(), symbol).Return(&ticker, nil).AnyTimes()
|
2022-07-22 06:53:17 +00:00
|
|
|
|
2022-09-09 09:40:17 +00:00
|
|
|
session := NewExchangeSession("test", mockEx)
|
2022-07-22 06:53:17 +00:00
|
|
|
session.Account.UpdateBalances(types.BalanceMap{
|
|
|
|
"BTC": {
|
|
|
|
Currency: "BTC",
|
|
|
|
Available: fixedpoint.Zero,
|
|
|
|
Locked: fixedpoint.Zero,
|
|
|
|
Borrowed: fixedpoint.NewFromFloat(1.0),
|
|
|
|
Interest: fixedpoint.NewFromFloat(0.003),
|
|
|
|
NetAsset: fixedpoint.Zero,
|
|
|
|
},
|
2024-10-05 05:09:31 +00:00
|
|
|
"USDT": Balance("USDT", Number(21000.0)),
|
2022-07-22 06:53:17 +00:00
|
|
|
})
|
|
|
|
assert.NotNil(t, session)
|
|
|
|
|
2024-10-04 11:45:07 +00:00
|
|
|
ctx := context.Background()
|
|
|
|
markets := AllMarkets()
|
|
|
|
priceSolver := pricesolver.NewSimplePriceResolver(markets)
|
2024-10-05 05:09:31 +00:00
|
|
|
err := priceSolver.UpdateFromTickers(ctx, mockEx, symbol)
|
2024-10-04 11:45:07 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
cal := NewAccountValueCalculator(session, priceSolver, "USDT")
|
2022-07-22 06:53:17 +00:00
|
|
|
assert.NotNil(t, cal)
|
|
|
|
|
2024-10-05 05:09:31 +00:00
|
|
|
marginLevel, err := cal.MarginLevel()
|
2022-07-22 06:53:17 +00:00
|
|
|
assert.NoError(t, err)
|
2022-07-22 07:06:10 +00:00
|
|
|
|
|
|
|
// expected (21000 / 19000 * 1.003)
|
|
|
|
assert.Equal(t,
|
|
|
|
fixedpoint.NewFromFloat(21000.0).Div(fixedpoint.NewFromFloat(19000.0).Mul(fixedpoint.NewFromFloat(1.003))).FormatString(6),
|
|
|
|
marginLevel.FormatString(6))
|
2022-07-22 06:53:17 +00:00
|
|
|
}
|
2022-09-11 16:05:22 +00:00
|
|
|
|
|
|
|
func number(n float64) fixedpoint.Value {
|
|
|
|
return fixedpoint.NewFromFloat(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_aggregateUsdValue(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
balances types.BalanceMap
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
want fixedpoint.Value
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "mixed",
|
|
|
|
args: args{
|
2024-10-04 15:46:43 +00:00
|
|
|
balances: BalancesFromText(`
|
|
|
|
USDC, 150.0
|
|
|
|
USDT, 100.0
|
|
|
|
BTC, 0.01
|
|
|
|
`),
|
2022-09-11 16:05:22 +00:00
|
|
|
},
|
2024-10-04 15:46:43 +00:00
|
|
|
want: Number(250.0),
|
2022-09-11 16:05:22 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2022-09-13 18:08:14 +00:00
|
|
|
assert.Equalf(t, tt.want, aggregateUsdNetValue(tt.args.balances), "aggregateUsdNetValue(%v)", tt.args.balances)
|
2022-09-11 16:05:22 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_usdFiatBalances(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
balances types.BalanceMap
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
wantFiats types.BalanceMap
|
|
|
|
wantRest types.BalanceMap
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
args: args{
|
2024-10-04 15:32:11 +00:00
|
|
|
balances: BalancesFromText(`
|
|
|
|
USDC, 150.0
|
|
|
|
USDT, 100.0
|
|
|
|
BTC, 0.01
|
|
|
|
`),
|
2022-09-11 16:05:22 +00:00
|
|
|
},
|
2024-10-04 15:32:11 +00:00
|
|
|
wantFiats: BalancesFromText(`
|
|
|
|
USDC, 150.0
|
|
|
|
USDT, 100.0
|
|
|
|
`),
|
|
|
|
wantRest: BalancesFromText(`
|
|
|
|
BTC, 0.01
|
|
|
|
`),
|
2022-09-11 16:05:22 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
gotFiats, gotRest := usdFiatBalances(tt.args.balances)
|
|
|
|
assert.Equalf(t, tt.wantFiats, gotFiats, "usdFiatBalances(%v)", tt.args.balances)
|
|
|
|
assert.Equalf(t, tt.wantRest, gotRest, "usdFiatBalances(%v)", tt.args.balances)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2022-09-13 18:08:14 +00:00
|
|
|
|
|
|
|
func Test_calculateNetValueInQuote(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
balances types.BalanceMap
|
2022-09-13 18:15:04 +00:00
|
|
|
prices types.PriceMap
|
2022-09-13 18:08:14 +00:00
|
|
|
quoteCurrency string
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
wantAccountValue fixedpoint.Value
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "positive asset",
|
|
|
|
args: args{
|
|
|
|
balances: types.BalanceMap{
|
2024-10-04 11:45:07 +00:00
|
|
|
"USDC": types.Balance{Currency: "USDC", Available: number(70.0 + 80.0)},
|
2022-09-13 18:08:14 +00:00
|
|
|
"USDT": types.Balance{Currency: "USDT", Available: number(100.0)},
|
|
|
|
"BTC": types.Balance{Currency: "BTC", Available: number(0.01)},
|
|
|
|
},
|
2022-09-13 18:15:04 +00:00
|
|
|
prices: types.PriceMap{
|
2024-10-04 11:45:07 +00:00
|
|
|
"USDCUSDT": Number(1.0),
|
|
|
|
"BTCUSDT": Number(19000.0),
|
2022-09-13 18:08:14 +00:00
|
|
|
},
|
|
|
|
quoteCurrency: "USDT",
|
|
|
|
},
|
2024-10-04 11:45:07 +00:00
|
|
|
wantAccountValue: Number(19000.0*0.01 + 100.0 + 80.0 + 70.0),
|
2022-09-13 18:08:14 +00:00
|
|
|
},
|
2022-09-13 18:17:21 +00:00
|
|
|
{
|
|
|
|
name: "reversed usdt price",
|
|
|
|
args: args{
|
|
|
|
balances: types.BalanceMap{
|
2024-10-04 11:45:07 +00:00
|
|
|
"USDC": types.Balance{Currency: "USDC", Available: Number(70.0 + 80.0)},
|
|
|
|
"TWD": types.Balance{Currency: "TWD", Available: Number(3000.0)},
|
|
|
|
"USDT": types.Balance{Currency: "USDT", Available: Number(100.0)},
|
|
|
|
"BTC": types.Balance{Currency: "BTC", Available: Number(0.01)},
|
2022-09-13 18:17:21 +00:00
|
|
|
},
|
|
|
|
prices: types.PriceMap{
|
2024-10-04 11:45:07 +00:00
|
|
|
"USDTTWD": Number(30.0),
|
|
|
|
"USDCUSDT": Number(1.0),
|
|
|
|
"BTCUSDT": Number(19000.0),
|
2022-09-13 18:17:21 +00:00
|
|
|
},
|
|
|
|
quoteCurrency: "USDT",
|
|
|
|
},
|
2024-10-04 11:45:07 +00:00
|
|
|
wantAccountValue: Number(19000.0*0.01 + 100.0 + 80.0 + 70.0 + (3000.0 / 30.0)),
|
2022-09-13 18:17:21 +00:00
|
|
|
},
|
2022-09-13 18:08:14 +00:00
|
|
|
{
|
|
|
|
name: "borrow base asset",
|
|
|
|
args: args{
|
|
|
|
balances: types.BalanceMap{
|
2024-10-04 11:45:07 +00:00
|
|
|
"USDT": types.Balance{Currency: "USDT", Available: Number(20000.0*2 + 80.0)},
|
|
|
|
"USDC": types.Balance{Currency: "USDC", Available: Number(70.0)},
|
|
|
|
"BTC": types.Balance{Currency: "BTC", Available: Number(0), Borrowed: Number(2.0)},
|
2022-09-13 18:08:14 +00:00
|
|
|
},
|
2022-09-13 18:15:04 +00:00
|
|
|
prices: types.PriceMap{
|
2022-09-13 18:08:14 +00:00
|
|
|
"USDCUSDT": 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{
|
2024-10-04 11:45:07 +00:00
|
|
|
"USDT": types.Balance{Currency: "USDT", Available: Number(20000.0*2 + 80.0)},
|
|
|
|
"USDC": types.Balance{Currency: "USDC", Available: Number(70.0)},
|
|
|
|
"ETH": types.Balance{Currency: "ETH", Available: Number(10.0)},
|
|
|
|
"BTC": types.Balance{Currency: "BTC", Available: Number(0), Borrowed: Number(2.0)},
|
2022-09-13 18:08:14 +00:00
|
|
|
},
|
2022-09-13 18:15:04 +00:00
|
|
|
prices: types.PriceMap{
|
2024-10-04 11:45:07 +00:00
|
|
|
"USDCUSDT": Number(1.0),
|
|
|
|
"BTCUSDT": Number(19000.0),
|
|
|
|
"ETHUSDT": Number(1700.0),
|
2022-09-13 18:08:14 +00:00
|
|
|
},
|
|
|
|
quoteCurrency: "USDT",
|
|
|
|
},
|
2024-10-04 11:45:07 +00:00
|
|
|
wantAccountValue: Number(19000.0*-2.0 + 1700.0*10.0 + 20000.0*2 + 80.0 + 70.0),
|
2022-09-13 18:08:14 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2024-10-04 11:45:07 +00:00
|
|
|
markets := AllMarkets()
|
|
|
|
priceSolver := pricesolver.NewSimplePriceResolver(markets)
|
|
|
|
|
|
|
|
for symbol, price := range tt.args.prices {
|
|
|
|
priceSolver.Update(symbol, price)
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.InDeltaf(t,
|
|
|
|
tt.wantAccountValue.Float64(),
|
|
|
|
calculateNetValueInQuote(tt.args.balances, priceSolver, tt.args.quoteCurrency).Float64(),
|
|
|
|
0.01,
|
|
|
|
"calculateNetValueInQuote(%v, %v, %v)", tt.args.balances, tt.args.prices, tt.args.quoteCurrency)
|
2022-09-13 18:08:14 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|