2022-09-09 09:40:17 +00:00
package bbgo
2022-07-22 03:55:06 +00:00
import (
"context"
"fmt"
"time"
2022-09-09 09:40:17 +00:00
log "github.com/sirupsen/logrus"
2022-07-22 03:55:06 +00:00
"github.com/c9s/bbgo/pkg/fixedpoint"
2022-09-09 09:40:17 +00:00
"github.com/c9s/bbgo/pkg/risk"
2022-07-22 03:55:06 +00:00
"github.com/c9s/bbgo/pkg/types"
)
2022-09-09 05:57:39 +00:00
var defaultLeverage = fixedpoint . NewFromInt ( 3 )
2022-07-22 03:55:06 +00:00
var maxLeverage = fixedpoint . NewFromInt ( 10 )
type AccountValueCalculator struct {
2022-09-09 09:40:17 +00:00
session * ExchangeSession
2022-07-22 03:55:06 +00:00
quoteCurrency string
prices map [ string ] fixedpoint . Value
tickers map [ string ] types . Ticker
updateTime time . Time
}
2022-09-09 09:40:17 +00:00
func NewAccountValueCalculator ( session * ExchangeSession , quoteCurrency string ) * AccountValueCalculator {
2022-07-22 03:55:06 +00:00
return & AccountValueCalculator {
session : session ,
quoteCurrency : quoteCurrency ,
prices : make ( map [ string ] fixedpoint . Value ) ,
tickers : make ( map [ string ] types . Ticker ) ,
}
}
func ( c * AccountValueCalculator ) UpdatePrices ( ctx context . Context ) error {
balances := c . session . Account . Balances ( )
currencies := balances . Currencies ( )
var symbols [ ] string
for _ , currency := range currencies {
2022-07-22 06:42:30 +00:00
if currency == c . quoteCurrency {
continue
}
2022-07-22 03:55:06 +00:00
symbol := currency + c . quoteCurrency
symbols = append ( symbols , symbol )
}
tickers , err := c . session . Exchange . QueryTickers ( ctx , symbols ... )
if err != nil {
return err
}
c . tickers = tickers
for symbol , ticker := range tickers {
c . prices [ symbol ] = ticker . Last
if ticker . Time . After ( c . updateTime ) {
c . updateTime = ticker . Time
}
}
return nil
}
func ( c * AccountValueCalculator ) DebtValue ( ctx context . Context ) ( fixedpoint . Value , error ) {
debtValue := fixedpoint . Zero
if len ( c . prices ) == 0 {
if err := c . UpdatePrices ( ctx ) ; err != nil {
return debtValue , err
}
}
balances := c . session . Account . Balances ( )
for _ , b := range balances {
symbol := b . Currency + c . quoteCurrency
price , ok := c . prices [ symbol ]
if ! ok {
continue
}
debtValue = debtValue . Add ( b . Debt ( ) . Mul ( price ) )
}
return debtValue , nil
}
2022-07-22 06:42:30 +00:00
func ( c * AccountValueCalculator ) MarketValue ( ctx context . Context ) ( fixedpoint . Value , error ) {
marketValue := fixedpoint . Zero
2022-07-22 03:55:06 +00:00
if len ( c . prices ) == 0 {
if err := c . UpdatePrices ( ctx ) ; err != nil {
2022-07-22 06:42:30 +00:00
return marketValue , err
2022-07-22 03:55:06 +00:00
}
}
balances := c . session . Account . Balances ( )
for _ , b := range balances {
2022-07-22 06:42:30 +00:00
if b . Currency == c . quoteCurrency {
marketValue = marketValue . Add ( b . Total ( ) )
continue
}
2022-07-22 03:55:06 +00:00
symbol := b . Currency + c . quoteCurrency
price , ok := c . prices [ symbol ]
if ! ok {
continue
}
2022-07-22 06:42:30 +00:00
marketValue = marketValue . Add ( b . Total ( ) . Mul ( price ) )
2022-07-22 03:55:06 +00:00
}
2022-07-22 06:42:30 +00:00
return marketValue , nil
2022-07-22 03:55:06 +00:00
}
2022-07-22 06:42:30 +00:00
func ( c * AccountValueCalculator ) NetValue ( ctx context . Context ) ( fixedpoint . Value , error ) {
2022-07-22 03:55:06 +00:00
accountValue := fixedpoint . Zero
2022-07-22 06:42:30 +00:00
if len ( c . prices ) == 0 {
if err := c . UpdatePrices ( ctx ) ; err != nil {
return accountValue , err
}
}
balances := c . session . Account . Balances ( )
for _ , b := range balances {
if b . Currency == c . quoteCurrency {
accountValue = accountValue . Add ( b . Net ( ) )
continue
}
symbol := b . Currency + c . quoteCurrency
price , ok := c . prices [ symbol ]
if ! ok {
continue
}
accountValue = accountValue . Add ( b . Net ( ) . Mul ( price ) )
2022-07-22 03:55:06 +00:00
}
2022-07-22 06:42:30 +00:00
return accountValue , nil
2022-07-22 03:55:06 +00:00
}
2022-08-05 07:38:19 +00:00
func ( c * AccountValueCalculator ) AvailableQuote ( ctx context . Context ) ( fixedpoint . Value , error ) {
2022-08-05 07:11:15 +00:00
accountValue := fixedpoint . Zero
if len ( c . prices ) == 0 {
if err := c . UpdatePrices ( ctx ) ; err != nil {
return accountValue , err
}
}
balances := c . session . Account . Balances ( )
for _ , b := range balances {
if b . Currency == c . quoteCurrency {
2022-08-13 05:28:45 +00:00
accountValue = accountValue . Add ( b . Net ( ) )
2022-08-05 07:11:15 +00:00
continue
}
symbol := b . Currency + c . quoteCurrency
price , ok := c . prices [ symbol ]
if ! ok {
continue
}
2022-08-13 05:28:45 +00:00
accountValue = accountValue . Add ( b . Net ( ) . Mul ( price ) )
2022-08-05 07:11:15 +00:00
}
return accountValue , nil
}
2022-07-22 06:54:25 +00:00
// MarginLevel calculates the margin level from the asset market value and the debt value
// See https://www.binance.com/en/support/faq/360030493931
2022-07-22 06:53:17 +00:00
func ( c * AccountValueCalculator ) MarginLevel ( ctx context . Context ) ( fixedpoint . Value , error ) {
marginLevel := fixedpoint . Zero
marketValue , err := c . MarketValue ( ctx )
if err != nil {
return marginLevel , err
}
debtValue , err := c . DebtValue ( ctx )
if err != nil {
return marginLevel , err
}
marginLevel = marketValue . Div ( debtValue )
return marginLevel , nil
}
2022-09-09 09:40:17 +00:00
func CalculateBaseQuantity ( session * ExchangeSession , market types . Market , price , quantity , leverage fixedpoint . Value ) ( fixedpoint . Value , error ) {
2022-07-22 04:04:43 +00:00
// default leverage guard
2022-07-22 03:55:06 +00:00
if leverage . IsZero ( ) {
2022-09-09 05:57:39 +00:00
leverage = defaultLeverage
2022-07-22 03:55:06 +00:00
}
2022-07-22 04:04:43 +00:00
baseBalance , _ := session . Account . Balance ( market . BaseCurrency )
quoteBalance , _ := session . Account . Balance ( market . QuoteCurrency )
2022-07-22 03:55:06 +00:00
usingLeverage := session . Margin || session . IsolatedMargin || session . Futures || session . IsolatedFutures
2022-07-22 04:04:43 +00:00
if ! usingLeverage {
// For spot, we simply sell the base quoteCurrency
balance , hasBalance := session . Account . Balance ( market . BaseCurrency )
if hasBalance {
if quantity . IsZero ( ) {
2022-09-09 09:40:17 +00:00
log . Warnf ( "sell quantity is not set, using all available base balance: %v" , balance )
2022-07-22 04:04:43 +00:00
if ! balance . Available . IsZero ( ) {
return balance . Available , nil
}
} else {
return fixedpoint . Min ( quantity , balance . Available ) , nil
}
2022-07-22 03:55:06 +00:00
}
2022-07-22 04:04:43 +00:00
return quantity , fmt . Errorf ( "quantity is zero, can not submit sell order, please check your quantity settings" )
}
2022-07-22 03:55:06 +00:00
2022-07-22 04:04:43 +00:00
if ! quantity . IsZero ( ) {
return quantity , nil
}
2022-07-22 03:55:06 +00:00
2022-07-26 17:28:18 +00:00
// using leverage -- starts from here
2022-09-09 09:40:17 +00:00
log . Infof ( "calculating available leveraged base quantity: base balance = %+v, quote balance = %+v" , baseBalance , quoteBalance )
2022-07-22 03:55:06 +00:00
2022-07-22 04:04:43 +00:00
// calculate the quantity automatically
if session . Margin || session . IsolatedMargin {
baseBalanceValue := baseBalance . Net ( ) . Mul ( price )
accountValue := baseBalanceValue . Add ( quoteBalance . Net ( ) )
2022-07-22 03:55:06 +00:00
2022-07-22 04:04:43 +00:00
// avoid using all account value since there will be some trade loss for interests and the fee
accountValue = accountValue . Mul ( one . Sub ( fixedpoint . NewFromFloat ( 0.01 ) ) )
2022-07-22 03:55:06 +00:00
2022-09-09 09:40:17 +00:00
log . Infof ( "calculated account value %f %s" , accountValue . Float64 ( ) , market . QuoteCurrency )
2022-07-22 03:55:06 +00:00
2022-07-22 04:04:43 +00:00
if session . IsolatedMargin {
originLeverage := leverage
leverage = fixedpoint . Min ( leverage , maxLeverage )
2022-09-09 09:40:17 +00:00
log . Infof ( "using isolated margin, maxLeverage=10 originalLeverage=%f currentLeverage=%f" ,
2022-07-22 04:04:43 +00:00
originLeverage . Float64 ( ) ,
2022-07-22 03:55:06 +00:00
leverage . Float64 ( ) )
}
2022-07-22 04:04:43 +00:00
// spot margin use the equity value, so we use the total quote balance here
2022-09-09 09:40:17 +00:00
maxPosition := risk . CalculateMaxPosition ( price , accountValue , leverage )
2022-07-22 04:04:43 +00:00
debt := baseBalance . Debt ( )
2022-07-26 03:47:07 +00:00
maxQuantity := maxPosition . Sub ( debt )
2022-07-22 03:55:06 +00:00
2022-09-09 09:40:17 +00:00
log . Infof ( "margin leverage: calculated maxQuantity=%f maxPosition=%f debt=%f price=%f accountValue=%f %s leverage=%f" ,
2022-07-26 03:47:07 +00:00
maxQuantity . Float64 ( ) ,
2022-07-22 04:04:43 +00:00
maxPosition . Float64 ( ) ,
debt . Float64 ( ) ,
price . Float64 ( ) ,
accountValue . Float64 ( ) ,
market . QuoteCurrency ,
leverage . Float64 ( ) )
2022-07-26 03:47:07 +00:00
return maxQuantity , nil
2022-07-22 03:55:06 +00:00
}
2022-07-22 04:04:43 +00:00
if session . Futures || session . IsolatedFutures {
// TODO: get mark price here
2022-09-09 09:40:17 +00:00
maxPositionQuantity := risk . CalculateMaxPosition ( price , quoteBalance . Available , leverage )
requiredPositionCost := risk . CalculatePositionCost ( price , price , maxPositionQuantity , leverage , types . SideTypeSell )
2022-07-22 04:04:43 +00:00
if quoteBalance . Available . Compare ( requiredPositionCost ) < 0 {
return maxPositionQuantity , fmt . Errorf ( "available margin %f %s is not enough, can not submit order" , quoteBalance . Available . Float64 ( ) , market . QuoteCurrency )
2022-07-22 03:55:06 +00:00
}
2022-07-22 04:04:43 +00:00
return maxPositionQuantity , nil
2022-07-22 03:55:06 +00:00
}
return quantity , fmt . Errorf ( "quantity is zero, can not submit sell order, please check your settings" )
}
2022-08-05 07:59:20 +00:00
2022-09-09 09:40:17 +00:00
func CalculateQuoteQuantity ( ctx context . Context , session * ExchangeSession , quoteCurrency string , leverage fixedpoint . Value ) ( fixedpoint . Value , error ) {
2022-08-05 07:59:20 +00:00
// default leverage guard
if leverage . IsZero ( ) {
2022-09-09 05:57:39 +00:00
leverage = defaultLeverage
2022-08-05 07:59:20 +00:00
}
quoteBalance , _ := session . Account . Balance ( quoteCurrency )
accountValue := NewAccountValueCalculator ( session , quoteCurrency )
usingLeverage := session . Margin || session . IsolatedMargin || session . Futures || session . IsolatedFutures
if ! usingLeverage {
// For spot, we simply return the quote balance
2022-08-05 08:28:42 +00:00
return quoteBalance . Available . Mul ( fixedpoint . Min ( leverage , fixedpoint . One ) ) , nil
2022-08-05 07:59:20 +00:00
}
// using leverage -- starts from here
availableQuote , err := accountValue . AvailableQuote ( ctx )
if err != nil {
log . WithError ( err ) . Errorf ( "can not update available quote" )
return fixedpoint . Zero , err
}
2022-09-09 09:40:17 +00:00
log . Infof ( "calculating available leveraged quote quantity: account available quote = %+v" , availableQuote )
2022-08-05 07:59:20 +00:00
2022-08-05 08:28:42 +00:00
return availableQuote . Mul ( leverage ) , nil
2022-08-05 07:59:20 +00:00
}