mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
risk: move leverage quantity calculation to the risk package
This commit is contained in:
parent
54affd2f99
commit
36cfaa924d
193
pkg/risk/account_value.go
Normal file
193
pkg/risk/account_value.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
balances := c.session.Account.Balances()
|
||||
for _, b := range balances {
|
||||
symbol := b.Currency + c.quoteCurrency
|
||||
price, ok := c.prices[symbol]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
accountValue = accountValue.Add(b.Net().Mul(price))
|
||||
}
|
||||
|
||||
return accountValue, nil
|
||||
}
|
||||
|
||||
func calculateAccountNetValue(session *bbgo.ExchangeSession) (fixedpoint.Value, error) {
|
||||
accountValue := fixedpoint.Zero
|
||||
ctx := context.Background()
|
||||
c := NewAccountValueCalculator(session, "USDT")
|
||||
if err := c.UpdatePrices(ctx); err != nil {
|
||||
return accountValue, err
|
||||
}
|
||||
|
||||
return c.NetValue(ctx)
|
||||
}
|
||||
|
||||
func CalculateBaseQuantity(session *bbgo.ExchangeSession, market types.Market, price, quantity, leverage fixedpoint.Value) (fixedpoint.Value, error) {
|
||||
if leverage.IsZero() {
|
||||
leverage = fixedpoint.NewFromInt(3)
|
||||
}
|
||||
|
||||
usingLeverage := session.Margin || session.IsolatedMargin || session.Futures || session.IsolatedFutures
|
||||
if usingLeverage {
|
||||
if !quantity.IsZero() {
|
||||
return quantity, nil
|
||||
}
|
||||
|
||||
// quantity is zero, we need to calculate the quantity
|
||||
baseBalance, _ := session.Account.Balance(market.BaseCurrency)
|
||||
quoteBalance, _ := session.Account.Balance(market.QuoteCurrency)
|
||||
|
||||
logrus.Infof("calculating leveraged quantity: base balance = %+v, quote balance = %+v", baseBalance, quoteBalance)
|
||||
|
||||
// calculate the quantity automatically
|
||||
if session.Margin || session.IsolatedMargin {
|
||||
baseBalanceValue := baseBalance.Net().Mul(price)
|
||||
accountValue := baseBalanceValue.Add(quoteBalance.Net())
|
||||
|
||||
// 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)))
|
||||
|
||||
logrus.Infof("calculated account value %f %s", accountValue.Float64(), market.QuoteCurrency)
|
||||
|
||||
if session.IsolatedMargin {
|
||||
originLeverage := leverage
|
||||
leverage = fixedpoint.Min(leverage, maxLeverage)
|
||||
logrus.Infof("using isolated margin, maxLeverage=10 originalLeverage=%f currentLeverage=%f",
|
||||
originLeverage.Float64(),
|
||||
leverage.Float64())
|
||||
}
|
||||
|
||||
// spot margin use the equity value, so we use the total quote balance here
|
||||
maxPosition := CalculateMaxPosition(price, accountValue, leverage)
|
||||
debt := baseBalance.Debt()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return maxPositionQuantity, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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, submitting sell with all base balance: %s", balance.Available.String())
|
||||
if !balance.Available.IsZero() {
|
||||
return balance.Available, nil
|
||||
}
|
||||
} else {
|
||||
return fixedpoint.Min(quantity, balance.Available), nil
|
||||
}
|
||||
}
|
||||
|
||||
return quantity, fmt.Errorf("quantity is zero, can not submit sell order, please check your settings")
|
||||
}
|
|
@ -2,8 +2,6 @@ package pivotshort
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
|
@ -108,7 +106,7 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
|
|||
s.Quantity.Float64(),
|
||||
s.Leverage.Float64())
|
||||
|
||||
quantity, err := useQuantityOrBaseBalance(s.session, s.Market, s.lastLow, s.Quantity, s.Leverage)
|
||||
quantity, err := risk.CalculateBaseQuantity(s.session, s.Market, s.lastLow, s.Quantity, s.Leverage)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("quantity calculation error")
|
||||
}
|
||||
|
@ -203,7 +201,7 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
|
|||
// graceful cancel all active orders
|
||||
_ = orderExecutor.GracefulCancel(ctx)
|
||||
|
||||
quantity, err := useQuantityOrBaseBalance(s.session, s.Market, closePrice, s.Quantity, s.Leverage)
|
||||
quantity, err := risk.CalculateBaseQuantity(s.session, s.Market, closePrice, s.Quantity, s.Leverage)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("quantity calculation error")
|
||||
}
|
||||
|
@ -240,179 +238,3 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
|
|||
}))
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
balances := c.session.Account.Balances()
|
||||
for _, b := range balances {
|
||||
symbol := b.Currency + c.quoteCurrency
|
||||
price, ok := c.prices[symbol]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
accountValue = accountValue.Add(b.Net().Mul(price))
|
||||
}
|
||||
|
||||
return accountValue, nil
|
||||
}
|
||||
|
||||
func calculateAccountNetValue(session *bbgo.ExchangeSession) (fixedpoint.Value, error) {
|
||||
accountValue := fixedpoint.Zero
|
||||
ctx := context.Background()
|
||||
c := NewAccountValueCalculator(session, "USDT")
|
||||
if err := c.UpdatePrices(ctx); err != nil {
|
||||
return accountValue, err
|
||||
}
|
||||
|
||||
return c.NetValue(ctx)
|
||||
}
|
||||
|
||||
func useQuantityOrBaseBalance(session *bbgo.ExchangeSession, market types.Market, price, quantity, leverage fixedpoint.Value) (fixedpoint.Value, error) {
|
||||
if leverage.IsZero() {
|
||||
leverage = fixedpoint.NewFromInt(3)
|
||||
}
|
||||
|
||||
usingLeverage := session.Margin || session.IsolatedMargin || session.Futures || session.IsolatedFutures
|
||||
if usingLeverage {
|
||||
if !quantity.IsZero() {
|
||||
return quantity, nil
|
||||
}
|
||||
|
||||
// quantity is zero, we need to calculate the quantity
|
||||
baseBalance, _ := session.Account.Balance(market.BaseCurrency)
|
||||
quoteBalance, _ := session.Account.Balance(market.QuoteCurrency)
|
||||
|
||||
log.Infof("calculating quantity: base balance = %+v, quote balance = %+v", baseBalance, quoteBalance)
|
||||
|
||||
// calculate the quantity automatically
|
||||
if session.Margin || session.IsolatedMargin {
|
||||
baseBalanceValue := baseBalance.Net().Mul(price)
|
||||
accountValue := baseBalanceValue.Add(quoteBalance.Net())
|
||||
|
||||
// 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)))
|
||||
|
||||
log.Infof("calculated account value %f %s", accountValue.Float64(), market.QuoteCurrency)
|
||||
|
||||
if session.IsolatedMargin {
|
||||
originLeverage := leverage
|
||||
leverage = fixedpoint.Min(leverage, fixedpoint.NewFromInt(10)) // max leverage is 10
|
||||
log.Infof("using isolated margin, maxLeverage=10 originalLeverage=%f currentLeverage=%f",
|
||||
originLeverage.Float64(),
|
||||
leverage.Float64())
|
||||
}
|
||||
|
||||
// spot margin use the equity value, so we use the total quote balance here
|
||||
maxPosition := risk.CalculateMaxPosition(price, accountValue, leverage)
|
||||
debt := baseBalance.Debt()
|
||||
|
||||
log.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
|
||||
}
|
||||
|
||||
if session.Futures || session.IsolatedFutures {
|
||||
// TODO: get mark price here
|
||||
maxPositionQuantity := risk.CalculateMaxPosition(price, quoteBalance.Available, leverage)
|
||||
requiredPositionCost := risk.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)
|
||||
}
|
||||
|
||||
return maxPositionQuantity, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// For spot, we simply sell the base quoteCurrency
|
||||
balance, hasBalance := session.Account.Balance(market.BaseCurrency)
|
||||
if hasBalance {
|
||||
if quantity.IsZero() {
|
||||
log.Warnf("sell quantity is not set, submitting sell with all base balance: %s", balance.Available.String())
|
||||
if !balance.Available.IsZero() {
|
||||
return balance.Available, nil
|
||||
}
|
||||
} else {
|
||||
return fixedpoint.Min(quantity, balance.Available), nil
|
||||
}
|
||||
}
|
||||
|
||||
return quantity, fmt.Errorf("quantity is zero, can not submit sell order, please check your settings")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user