2022-07-22 03:55:06 +00:00
package risk
import (
"context"
"fmt"
"time"
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
var one = fixedpoint . One
var maxLeverage = fixedpoint . NewFromInt ( 10 )
type AccountValueCalculator struct {
session * bbgo . ExchangeSession
quoteCurrency string
prices map [ string ] fixedpoint . Value
tickers map [ string ] types . Ticker
updateTime time . Time
}
func NewAccountValueCalculator ( session * bbgo . ExchangeSession , quoteCurrency string ) * AccountValueCalculator {
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-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-07-22 03:55:06 +00:00
func CalculateBaseQuantity ( session * bbgo . 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 ( ) {
leverage = fixedpoint . NewFromInt ( 3 )
}
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 ( ) {
logrus . Warnf ( "sell quantity is not set, using all available base balance: %v" , balance )
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
// using leverage -- starts from here
if ! quantity . IsZero ( ) {
return quantity , nil
}
2022-07-22 03:55:06 +00:00
2022-07-22 04:04:43 +00:00
logrus . 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-07-22 04:04:43 +00:00
logrus . 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 )
logrus . Infof ( "using isolated margin, maxLeverage=10 originalLeverage=%f currentLeverage=%f" ,
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
maxPosition := CalculateMaxPosition ( price , accountValue , leverage )
debt := baseBalance . Debt ( )
2022-07-22 03:55:06 +00:00
2022-07-22 04:04:43 +00:00
logrus . Infof ( "margin leverage: calculated maxPosition=%f debt=%f price=%f accountValue=%f %s leverage=%f" ,
maxPosition . Float64 ( ) ,
debt . Float64 ( ) ,
price . Float64 ( ) ,
accountValue . Float64 ( ) ,
market . QuoteCurrency ,
leverage . Float64 ( ) )
return maxPosition . Sub ( debt ) , 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
maxPositionQuantity := CalculateMaxPosition ( price , quoteBalance . Available , leverage )
requiredPositionCost := CalculatePositionCost ( price , price , maxPositionQuantity , leverage , types . SideTypeSell )
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" )
}