all: refactor NewAccountValueCalculator

This commit is contained in:
c9s 2024-10-05 13:09:31 +08:00
parent a718e30bb4
commit 6079e7b06a
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
8 changed files with 144 additions and 192 deletions

View File

@ -39,7 +39,6 @@ func NewAccountValueCalculator(
priceSolver: priceSolver, priceSolver: priceSolver,
session: session, session: session,
quoteCurrency: quoteCurrency, quoteCurrency: quoteCurrency,
prices: make(map[string]fixedpoint.Value),
tickers: make(map[string]types.Ticker), tickers: make(map[string]types.Ticker),
} }
} }
@ -62,99 +61,69 @@ func (c *AccountValueCalculator) UpdatePrices(ctx context.Context) error {
return c.priceSolver.UpdateFromTickers(ctx, c.session.Exchange, symbols...) return c.priceSolver.UpdateFromTickers(ctx, c.session.Exchange, symbols...)
} }
func (c *AccountValueCalculator) DebtValue(ctx context.Context) (fixedpoint.Value, error) { func (c *AccountValueCalculator) DebtValue() fixedpoint.Value {
debtValue := fixedpoint.Zero
if len(c.prices) == 0 {
if err := c.UpdatePrices(ctx); err != nil {
return debtValue, err
}
}
balances := c.session.Account.Balances() balances := c.session.Account.Balances()
for _, b := range balances { return totalValueInQuote(balances, c.priceSolver, c.quoteCurrency, func(
symbol := b.Currency + c.quoteCurrency prev fixedpoint.Value, b types.Balance, price fixedpoint.Value,
price, ok := c.prices[symbol] ) fixedpoint.Value {
if !ok { return prev.Add(b.Debt().Mul(price))
continue })
}
debtValue = debtValue.Add(b.Debt().Mul(price))
}
return debtValue, nil
} }
func (c *AccountValueCalculator) MarketValue(ctx context.Context) (fixedpoint.Value, error) { func (c *AccountValueCalculator) MarketValue() fixedpoint.Value {
marketValue := fixedpoint.Zero
if len(c.prices) == 0 {
if err := c.UpdatePrices(ctx); err != nil {
return marketValue, err
}
}
balances := c.session.Account.Balances() balances := c.session.Account.Balances()
for _, b := range balances { return totalValueInQuote(balances, c.priceSolver, c.quoteCurrency, func(
if b.Currency == c.quoteCurrency { prev fixedpoint.Value, b types.Balance, price fixedpoint.Value,
marketValue = marketValue.Add(b.Total()) ) fixedpoint.Value {
continue return prev.Add(b.Total().Mul(price))
} })
if c.priceSolver != nil {
if price, ok := c.priceSolver.ResolvePrice(b.Currency, c.quoteCurrency); ok {
marketValue = marketValue.Add(b.Total().Mul(price))
}
} else {
symbol := b.Currency + c.quoteCurrency
if price, ok := c.prices[symbol]; ok {
marketValue = marketValue.Add(b.Total().Mul(price))
}
}
}
return marketValue, nil
} }
func (c *AccountValueCalculator) NetValue(ctx context.Context) (fixedpoint.Value, error) { func (c *AccountValueCalculator) NetValue() fixedpoint.Value {
if len(c.prices) == 0 { balances := c.session.Account.Balances()
if err := c.UpdatePrices(ctx); err != nil { return totalValueInQuote(balances, c.priceSolver, c.quoteCurrency, func(
return fixedpoint.Zero, err prev fixedpoint.Value, b types.Balance, price fixedpoint.Value,
) fixedpoint.Value {
return prev.Add(b.Net().Mul(price))
})
}
func totalValueInQuote(
balances types.BalanceMap,
priceSolver *pricesolver.SimplePriceSolver,
quoteCurrency string,
algo func(prev fixedpoint.Value, b types.Balance, price fixedpoint.Value) fixedpoint.Value,
) (totalValue fixedpoint.Value) {
totalValue = fixedpoint.Zero
for _, b := range balances {
if b.Currency == quoteCurrency {
totalValue = algo(totalValue, b, fixedpoint.One)
continue
} else if price, ok := priceSolver.ResolvePrice(b.Currency, quoteCurrency); ok {
totalValue = algo(totalValue, b, price)
} }
} }
balances := c.session.Account.Balances() return totalValue
accountValue := calculateNetValueInQuote(balances, c.priceSolver, c.quoteCurrency)
return accountValue, nil
} }
func calculateNetValueInQuote( func calculateNetValueInQuote(
balances types.BalanceMap, priceSolver *pricesolver.SimplePriceSolver, quoteCurrency string, balances types.BalanceMap,
) (accountValue fixedpoint.Value) { priceSolver *pricesolver.SimplePriceSolver,
accountValue = fixedpoint.Zero quoteCurrency string,
for _, b := range balances { ) fixedpoint.Value {
if b.Currency == quoteCurrency { return totalValueInQuote(balances, priceSolver, quoteCurrency, func(
accountValue = accountValue.Add(b.Net()) prev fixedpoint.Value, b types.Balance, price fixedpoint.Value,
continue ) fixedpoint.Value {
} return prev.Add(b.Net().Mul(price))
})
if price, ok := priceSolver.ResolvePrice(b.Currency, quoteCurrency); ok {
accountValue = accountValue.Add(b.Net().Mul(price))
}
}
return accountValue
} }
func (c *AccountValueCalculator) AvailableQuote(ctx context.Context) (fixedpoint.Value, error) { func (c *AccountValueCalculator) AvailableQuote() (fixedpoint.Value, error) {
accountValue := fixedpoint.Zero accountValue := fixedpoint.Zero
if len(c.prices) == 0 {
if err := c.UpdatePrices(ctx); err != nil {
return accountValue, err
}
}
balances := c.session.Account.Balances() balances := c.session.Account.Balances()
for _, b := range balances { for _, b := range balances {
if b.Currency == c.quoteCurrency { if b.Currency == c.quoteCurrency {
@ -162,13 +131,9 @@ func (c *AccountValueCalculator) AvailableQuote(ctx context.Context) (fixedpoint
continue continue
} }
symbol := b.Currency + c.quoteCurrency if price, ok := c.priceSolver.ResolvePrice(b.Currency, c.quoteCurrency); ok {
price, ok := c.prices[symbol] accountValue = accountValue.Add(b.Net().Mul(price))
if !ok {
continue
} }
accountValue = accountValue.Add(b.Net().Mul(price))
} }
return accountValue, nil return accountValue, nil
@ -176,20 +141,15 @@ func (c *AccountValueCalculator) AvailableQuote(ctx context.Context) (fixedpoint
// MarginLevel calculates the margin level from the asset market value and the debt value // MarginLevel calculates the margin level from the asset market value and the debt value
// See https://www.binance.com/en/support/faq/360030493931 // See https://www.binance.com/en/support/faq/360030493931
func (c *AccountValueCalculator) MarginLevel(ctx context.Context) (fixedpoint.Value, error) { func (c *AccountValueCalculator) MarginLevel() (fixedpoint.Value, error) {
marginLevel := fixedpoint.Zero marketValue := c.MarketValue()
marketValue, err := c.MarketValue(ctx) debtValue := c.DebtValue()
if err != nil {
return marginLevel, err if marketValue.IsZero() || debtValue.IsZero() {
return fixedpoint.NewFromFloat(999.0), nil
} }
debtValue, err := c.DebtValue(ctx) return marketValue.Div(debtValue), nil
if err != nil {
return marginLevel, err
}
marginLevel = marketValue.Div(debtValue)
return marginLevel, nil
} }
func aggregateUsdNetValue(balances types.BalanceMap) fixedpoint.Value { func aggregateUsdNetValue(balances types.BalanceMap) fixedpoint.Value {
@ -255,11 +215,7 @@ func CalculateBaseQuantity(
totalUsdValue = aggregateUsdNetValue(balances) totalUsdValue = aggregateUsdNetValue(balances)
} else if len(restBalances) > 1 { } else if len(restBalances) > 1 {
accountValue := NewAccountValueCalculator(session, nil, "USDT") accountValue := NewAccountValueCalculator(session, nil, "USDT")
netValue, err := accountValue.NetValue(context.Background()) netValue := accountValue.NetValue()
if err != nil {
return quantity, err
}
totalUsdValue = netValue totalUsdValue = netValue
} else { } else {
// TODO: translate quote currency like BTC of ETH/BTC to usd value // TODO: translate quote currency like BTC of ETH/BTC to usd value
@ -362,7 +318,7 @@ func CalculateQuoteQuantity(
// using leverage -- starts from here // using leverage -- starts from here
accountValue := NewAccountValueCalculator(session, nil, quoteCurrency) accountValue := NewAccountValueCalculator(session, nil, quoteCurrency)
availableQuote, err := accountValue.AvailableQuote(ctx) availableQuote, err := accountValue.AvailableQuote()
if err != nil { if err != nil {
log.WithError(err).Errorf("can not update available quote") log.WithError(err).Errorf("can not update available quote")
return fixedpoint.Zero, err return fixedpoint.Zero, err

View File

@ -3,7 +3,6 @@ package bbgo
import ( import (
"context" "context"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock" "go.uber.org/mock/gomock"
@ -15,19 +14,6 @@ import (
"github.com/c9s/bbgo/pkg/types/mocks" "github.com/c9s/bbgo/pkg/types/mocks"
) )
func newTestTicker() types.Ticker {
return types.Ticker{
Time: time.Now(),
Volume: fixedpoint.Zero,
Last: fixedpoint.NewFromFloat(19000.0),
Open: fixedpoint.NewFromFloat(19500.0),
High: fixedpoint.NewFromFloat(19900.0),
Low: fixedpoint.NewFromFloat(18800.0),
Buy: fixedpoint.NewFromFloat(19500.0),
Sell: fixedpoint.NewFromFloat(18900.0),
}
}
func TestAccountValueCalculator_NetValue(t *testing.T) { func TestAccountValueCalculator_NetValue(t *testing.T) {
symbol := "BTCUSDT" symbol := "BTCUSDT"
markets := AllMarkets() markets := AllMarkets()
@ -36,12 +22,11 @@ func TestAccountValueCalculator_NetValue(t *testing.T) {
mockCtrl := gomock.NewController(t) mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish() defer mockCtrl.Finish()
ticker := Ticker(symbol)
mockEx := mocks.NewMockExchange(mockCtrl) mockEx := mocks.NewMockExchange(mockCtrl)
// for market data stream and user data stream // for market data stream and user data stream
mockEx.EXPECT().NewStream().Return(&types.StandardStream{}).Times(2) mockEx.EXPECT().NewStream().Return(&types.StandardStream{}).Times(2)
mockEx.EXPECT().QueryTickers(gomock.Any(), []string{symbol}).Return(map[string]types.Ticker{ mockEx.EXPECT().QueryTicker(gomock.Any(), symbol).Return(&ticker, nil).AnyTimes()
"BTCUSDT": Ticker(symbol),
}, nil)
session := NewExchangeSession("test", mockEx) session := NewExchangeSession("test", mockEx)
session.Account.UpdateBalances(types.BalanceMap{ session.Account.UpdateBalances(types.BalanceMap{
@ -64,14 +49,12 @@ func TestAccountValueCalculator_NetValue(t *testing.T) {
}) })
assert.NotNil(t, session) assert.NotNil(t, session)
ctx := context.Background()
priceSolver := pricesolver.NewSimplePriceResolver(markets) priceSolver := pricesolver.NewSimplePriceResolver(markets)
priceSolver.Update(symbol, ticker.GetValidPrice())
cal := NewAccountValueCalculator(session, priceSolver, "USDT") cal := NewAccountValueCalculator(session, priceSolver, "USDT")
assert.NotNil(t, cal)
netValue, err := cal.NetValue(ctx) netValue := cal.NetValue()
assert.NoError(t, err)
assert.Equal(t, "20000", netValue.String()) assert.Equal(t, "20000", netValue.String())
}) })
@ -79,12 +62,12 @@ func TestAccountValueCalculator_NetValue(t *testing.T) {
mockCtrl := gomock.NewController(t) mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish() defer mockCtrl.Finish()
ticker := Ticker(symbol)
mockEx := mocks.NewMockExchange(mockCtrl) mockEx := mocks.NewMockExchange(mockCtrl)
// for market data stream and user data stream // for market data stream and user data stream
mockEx.EXPECT().NewStream().Return(&types.StandardStream{}).Times(2) mockEx.EXPECT().NewStream().Return(&types.StandardStream{}).Times(2)
mockEx.EXPECT().QueryTickers(gomock.Any(), []string{symbol}).Return(map[string]types.Ticker{ mockEx.EXPECT().QueryTicker(gomock.Any(), symbol).Return(&ticker, nil).AnyTimes()
symbol: Ticker(symbol),
}, nil)
session := NewExchangeSession("test", mockEx) session := NewExchangeSession("test", mockEx)
session.Account.UpdateBalances(types.BalanceMap{ session.Account.UpdateBalances(types.BalanceMap{
@ -105,16 +88,12 @@ func TestAccountValueCalculator_NetValue(t *testing.T) {
NetAsset: fixedpoint.Zero, NetAsset: fixedpoint.Zero,
}, },
}) })
assert.NotNil(t, session)
ctx := context.Background()
priceSolver := pricesolver.NewSimplePriceResolver(markets) priceSolver := pricesolver.NewSimplePriceResolver(markets)
priceSolver.Update(symbol, ticker.GetValidPrice())
cal := NewAccountValueCalculator(session, priceSolver, "USDT") cal := NewAccountValueCalculator(session, priceSolver, "USDT")
assert.NotNil(t, cal) netValue := cal.NetValue()
netValue, err := cal.NetValue(ctx)
assert.NoError(t, err)
assert.Equal(t, "2000", netValue.String()) // 21000-19000 assert.Equal(t, "2000", netValue.String()) // 21000-19000
}) })
} }
@ -123,14 +102,13 @@ func TestNewAccountValueCalculator_MarginLevel(t *testing.T) {
mockCtrl := gomock.NewController(t) mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish() defer mockCtrl.Finish()
ticker := newTestTicker() symbol := "BTCUSDT"
ticker := Ticker(symbol)
mockEx := mocks.NewMockExchange(mockCtrl) mockEx := mocks.NewMockExchange(mockCtrl)
// for market data stream and user data stream // for market data stream and user data stream
mockEx.EXPECT().NewStream().Return(&types.StandardStream{}).Times(2) mockEx.EXPECT().NewStream().Return(&types.StandardStream{}).Times(2)
mockEx.EXPECT().QueryTickers(gomock.Any(), []string{"BTCUSDT"}).Return(map[string]types.Ticker{ mockEx.EXPECT().QueryTicker(gomock.Any(), symbol).Return(&ticker, nil).AnyTimes()
"BTCUSDT": ticker,
}, nil)
session := NewExchangeSession("test", mockEx) session := NewExchangeSession("test", mockEx)
session.Account.UpdateBalances(types.BalanceMap{ session.Account.UpdateBalances(types.BalanceMap{
@ -142,27 +120,20 @@ func TestNewAccountValueCalculator_MarginLevel(t *testing.T) {
Interest: fixedpoint.NewFromFloat(0.003), Interest: fixedpoint.NewFromFloat(0.003),
NetAsset: fixedpoint.Zero, NetAsset: fixedpoint.Zero,
}, },
"USDT": { "USDT": Balance("USDT", Number(21000.0)),
Currency: "USDT",
Available: fixedpoint.NewFromFloat(21000.0),
Locked: fixedpoint.Zero,
Borrowed: fixedpoint.Zero,
Interest: fixedpoint.Zero,
NetAsset: fixedpoint.Zero,
},
}) })
assert.NotNil(t, session) assert.NotNil(t, session)
ctx := context.Background() ctx := context.Background()
markets := AllMarkets() markets := AllMarkets()
priceSolver := pricesolver.NewSimplePriceResolver(markets) priceSolver := pricesolver.NewSimplePriceResolver(markets)
err := priceSolver.UpdateFromTickers(ctx, mockEx, "BTCUSDT") err := priceSolver.UpdateFromTickers(ctx, mockEx, symbol)
assert.NoError(t, err) assert.NoError(t, err)
cal := NewAccountValueCalculator(session, priceSolver, "USDT") cal := NewAccountValueCalculator(session, priceSolver, "USDT")
assert.NotNil(t, cal) assert.NotNil(t, cal)
marginLevel, err := cal.MarginLevel(ctx) marginLevel, err := cal.MarginLevel()
assert.NoError(t, err) assert.NoError(t, err)
// expected (21000 / 19000 * 1.003) // expected (21000 / 19000 * 1.003)

View File

@ -99,7 +99,6 @@ func (m *SimplePriceSolver) UpdateFromTickers(ctx context.Context, ex types.Exch
} }
func (m *SimplePriceSolver) inferencePrice(asset string, assetPrice fixedpoint.Value, preferredFiats ...string) (fixedpoint.Value, bool) { func (m *SimplePriceSolver) inferencePrice(asset string, assetPrice fixedpoint.Value, preferredFiats ...string) (fixedpoint.Value, bool) {
// log.Infof("inferencePrice %s = %f", asset, assetPrice.Float64())
quotePrices, ok := m.pricesByBase[asset] quotePrices, ok := m.pricesByBase[asset]
if ok { if ok {
for quote, price := range quotePrices { for quote, price := range quotePrices {
@ -122,10 +121,8 @@ func (m *SimplePriceSolver) inferencePrice(asset string, assetPrice fixedpoint.V
basePrices, ok := m.pricesByQuote[asset] basePrices, ok := m.pricesByQuote[asset]
if ok { if ok {
for base, basePrice := range basePrices { for base, basePrice := range basePrices {
// log.Infof("base %s @ %s", base, basePrice.String())
for _, fiat := range preferredFiats { for _, fiat := range preferredFiats {
if base == fiat { if base == fiat {
// log.Infof("ret %f / %f = %f", assetPrice.Float64(), basePrice.Float64(), assetPrice.Div(basePrice).Float64())
return assetPrice.Div(basePrice), true return assetPrice.Div(basePrice), true
} }
} }
@ -142,6 +139,12 @@ func (m *SimplePriceSolver) inferencePrice(asset string, assetPrice fixedpoint.V
} }
func (m *SimplePriceSolver) ResolvePrice(asset string, preferredFiats ...string) (fixedpoint.Value, bool) { func (m *SimplePriceSolver) ResolvePrice(asset string, preferredFiats ...string) (fixedpoint.Value, bool) {
if len(preferredFiats) == 0 {
return fixedpoint.Zero, false
} else if asset == preferredFiats[0] {
return fixedpoint.One, true
}
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
return m.inferencePrice(asset, fixedpoint.One, preferredFiats...) return m.inferencePrice(asset, fixedpoint.One, preferredFiats...)

View File

@ -6,13 +6,14 @@ import (
"os" "os"
"sync" "sync"
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/data/tsv" "github.com/c9s/bbgo/pkg/data/tsv"
"github.com/c9s/bbgo/pkg/datatype/floats" "github.com/c9s/bbgo/pkg/datatype/floats"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/indicator" "github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
"github.com/sirupsen/logrus"
) )
const ID = "harmonic" const ID = "harmonic"
@ -29,7 +30,7 @@ type Strategy struct {
Market types.Market Market types.Market
types.IntervalWindow types.IntervalWindow
//bbgo.OpenPositionOptions // bbgo.OpenPositionOptions
// persistence fields // persistence fields
Position *types.Position `persistence:"position"` Position *types.Position `persistence:"position"`
@ -239,7 +240,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
// Cancel active orders // Cancel active orders
_ = s.orderExecutor.GracefulCancel(ctx) _ = s.orderExecutor.GracefulCancel(ctx)
// Close 100% position // Close 100% position
//_ = s.ClosePosition(ctx, fixedpoint.One) // _ = s.ClosePosition(ctx, fixedpoint.One)
}) })
s.session = session s.session = session
@ -258,7 +259,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.orderExecutor.BindTradeStats(s.TradeStats) s.orderExecutor.BindTradeStats(s.TradeStats)
// AccountValueCalculator // AccountValueCalculator
s.AccountValueCalculator = bbgo.NewAccountValueCalculator(s.session, s.Market.QuoteCurrency) s.AccountValueCalculator = bbgo.NewAccountValueCalculator(s.session, nil, s.Market.QuoteCurrency)
// Accumulated profit report // Accumulated profit report
if bbgo.IsBackTesting { if bbgo.IsBackTesting {

View File

@ -255,7 +255,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.orderExecutor.BindTradeStats(s.TradeStats) s.orderExecutor.BindTradeStats(s.TradeStats)
// AccountValueCalculator // AccountValueCalculator
s.AccountValueCalculator = bbgo.NewAccountValueCalculator(s.session, s.Market.QuoteCurrency) s.AccountValueCalculator = bbgo.NewAccountValueCalculator(s.session, nil, s.Market.QuoteCurrency)
// Accumulated profit report // Accumulated profit report
if bbgo.IsBackTesting { if bbgo.IsBackTesting {

View File

@ -255,7 +255,9 @@ func (s *Strategy) getSide(stSignal types.Direction, demaSignal types.Direction,
return side return side
} }
func (s *Strategy) generateOrderForm(side types.SideType, quantity fixedpoint.Value, marginOrderSideEffect types.MarginOrderSideEffectType) types.SubmitOrder { func (s *Strategy) generateOrderForm(
side types.SideType, quantity fixedpoint.Value, marginOrderSideEffect types.MarginOrderSideEffectType,
) types.SubmitOrder {
orderForm := types.SubmitOrder{ orderForm := types.SubmitOrder{
Symbol: s.Symbol, Symbol: s.Symbol,
Market: s.Market, Market: s.Market,
@ -382,7 +384,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
// AccountValueCalculator // AccountValueCalculator
s.AccountValueCalculator = bbgo.NewAccountValueCalculator(s.session, s.Market.QuoteCurrency) s.AccountValueCalculator = bbgo.NewAccountValueCalculator(s.session, nil, s.Market.QuoteCurrency)
// For drawing // For drawing
profitSlice := floats.Slice{1., 1.} profitSlice := floats.Slice{1., 1.}

View File

@ -187,6 +187,8 @@ type Strategy struct {
logger logrus.FieldLogger logger logrus.FieldLogger
metricsLabels prometheus.Labels metricsLabels prometheus.Labels
connectivityGroup *types.ConnectivityGroup
} }
func (s *Strategy) ID() string { func (s *Strategy) ID() string {
@ -654,40 +656,37 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
hedgeAccount.MarginLevel.String(), hedgeAccount.MarginLevel.String(),
s.MinMarginLevel.String()) s.MinMarginLevel.String())
netValueInUsd, calcErr := s.accountValueCalculator.NetValue(ctx) netValueInUsd := s.accountValueCalculator.NetValue()
if calcErr != nil {
s.logger.WithError(calcErr).Errorf("unable to calculate the net value")
} else {
// calculate credit buffer
s.logger.Infof("hedge account net value in usd: %f", netValueInUsd.Float64())
maximumValueInUsd := netValueInUsd.Mul(s.MaxHedgeAccountLeverage) // calculate credit buffer
s.logger.Infof("hedge account net value in usd: %f", netValueInUsd.Float64())
s.logger.Infof("hedge account maximum leveraged value in usd: %f (%f x)", maximumValueInUsd.Float64(), s.MaxHedgeAccountLeverage.Float64()) maximumValueInUsd := netValueInUsd.Mul(s.MaxHedgeAccountLeverage)
if quote, ok := hedgeAccount.Balance(s.sourceMarket.QuoteCurrency); ok { s.logger.Infof("hedge account maximum leveraged value in usd: %f (%f x)", maximumValueInUsd.Float64(), s.MaxHedgeAccountLeverage.Float64())
debt := quote.Debt()
quota := maximumValueInUsd.Sub(debt)
s.logger.Infof("hedge account quote balance: %s, debt: %s, quota: %s", if quote, ok := hedgeAccount.Balance(s.sourceMarket.QuoteCurrency); ok {
quote.String(), debt := quote.Debt()
debt.String(), quota := maximumValueInUsd.Sub(debt)
quota.String())
hedgeQuota.QuoteAsset.Add(quota) s.logger.Infof("hedge account quote balance: %s, debt: %s, quota: %s",
} quote.String(),
debt.String(),
quota.String())
if base, ok := hedgeAccount.Balance(s.sourceMarket.BaseCurrency); ok { hedgeQuota.QuoteAsset.Add(quota)
debt := base.Debt() }
quota := maximumValueInUsd.Div(bestAsk.Price).Sub(debt)
s.logger.Infof("hedge account base balance: %s, debt: %s, quota: %s", if base, ok := hedgeAccount.Balance(s.sourceMarket.BaseCurrency); ok {
base.String(), debt := base.Debt()
debt.String(), quota := maximumValueInUsd.Div(bestAsk.Price).Sub(debt)
quota.String())
hedgeQuota.BaseAsset.Add(quota) s.logger.Infof("hedge account base balance: %s, debt: %s, quota: %s",
} base.String(),
debt.String(),
quota.String())
hedgeQuota.BaseAsset.Add(quota)
} }
} }
} else { } else {
@ -1322,12 +1321,7 @@ func (s *Strategy) accountUpdater(ctx context.Context) {
return return
} }
netValue, err := s.accountValueCalculator.NetValue(ctx) netValue := s.accountValueCalculator.NetValue()
if err != nil {
log.WithError(err).Errorf("unable to update account")
return
}
s.logger.Infof("hedge session net value ~= %f USD", netValue.Float64()) s.logger.Infof("hedge session net value ~= %f USD", netValue.Float64())
} }
} }
@ -1419,7 +1413,7 @@ func (s *Strategy) CrossRun(
return fmt.Errorf("maker session market %s is not defined", s.Symbol) return fmt.Errorf("maker session market %s is not defined", s.Symbol)
} }
s.accountValueCalculator = bbgo.NewAccountValueCalculator(s.sourceSession, s.sourceMarket.QuoteCurrency) s.accountValueCalculator = bbgo.NewAccountValueCalculator(s.sourceSession, nil, s.sourceMarket.QuoteCurrency)
indicators := s.sourceSession.Indicators(s.Symbol) indicators := s.sourceSession.Indicators(s.Symbol)
@ -1622,13 +1616,27 @@ func (s *Strategy) CrossRun(
s.stopC = make(chan struct{}) s.stopC = make(chan struct{})
sourceConnectivity := types.NewConnectivity()
sourceConnectivity.Bind(s.sourceSession.UserDataStream)
s.connectivityGroup = types.NewConnectivityGroup(sourceConnectivity)
if s.RecoverTrade { if s.RecoverTrade {
go s.tradeRecover(ctx) go s.tradeRecover(ctx)
} }
go s.accountUpdater(ctx) go func() {
go s.hedgeWorker(ctx) select {
go s.quoteWorker(ctx) case <-ctx.Done():
case <-s.connectivityGroup.AllAuthedC(ctx, 15*time.Second):
}
s.logger.Infof("all user data streams are connected, starting workers...")
go s.accountUpdater(ctx)
go s.hedgeWorker(ctx)
go s.quoteWorker(ctx)
}()
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) { bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
// the ctx here is the shutdown context (not the strategy context) // the ctx here is the shutdown context (not the strategy context)

View File

@ -30,12 +30,23 @@ var _tickers = map[string]types.Ticker{
Buy: fixedpoint.NewFromFloat(2519.0), Buy: fixedpoint.NewFromFloat(2519.0),
Sell: fixedpoint.NewFromFloat(2521.0), Sell: fixedpoint.NewFromFloat(2521.0),
}, },
"USDTTWD": {
Time: time.Now(),
Volume: fixedpoint.Zero,
Open: fixedpoint.NewFromFloat(32.1),
High: fixedpoint.NewFromFloat(32.31),
Low: fixedpoint.NewFromFloat(32.01),
Last: fixedpoint.NewFromFloat(32.0),
Buy: fixedpoint.NewFromFloat(32.0),
Sell: fixedpoint.NewFromFloat(32.01),
},
} }
func Ticker(symbol string) types.Ticker { func Ticker(symbol string) types.Ticker {
ticker, ok := _tickers[symbol] ticker, ok := _tickers[symbol]
if !ok { if !ok {
panic(fmt.Errorf("%s test ticker not found, valid tickers: %+v", symbol, []string{"BTCUSDT", "ETHUSDT"})) panic(fmt.Errorf("%s test ticker not found, valid tickers: %+v", symbol, []string{"BTCUSDT", "ETHUSDT", "USDTTWD"}))
} }
return ticker return ticker