mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Merge pull request #827 from c9s/strategy/pivotshort
strategy/pivotshort: improve quantity calculation for margin and futures
This commit is contained in:
commit
191e00adeb
29
pkg/exchange/util.go
Normal file
29
pkg/exchange/util.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package exchange
|
||||
|
||||
import "github.com/c9s/bbgo/pkg/types"
|
||||
|
||||
func GetSessionAttributes(exchange types.Exchange) (isMargin, isFutures, isIsolated bool, isolatedSymbol string) {
|
||||
if marginExchange, ok := exchange.(types.MarginExchange); ok {
|
||||
marginSettings := marginExchange.GetMarginSettings()
|
||||
isMargin = marginSettings.IsMargin
|
||||
if isMargin {
|
||||
isIsolated = marginSettings.IsIsolatedMargin
|
||||
if marginSettings.IsIsolatedMargin {
|
||||
isolatedSymbol = marginSettings.IsolatedMarginSymbol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if futuresExchange, ok := exchange.(types.FuturesExchange); ok {
|
||||
futuresSettings := futuresExchange.GetFuturesSettings()
|
||||
isFutures = futuresSettings.IsFutures
|
||||
if isFutures {
|
||||
isIsolated = futuresSettings.IsIsolatedFutures
|
||||
if futuresSettings.IsIsolatedFutures {
|
||||
isolatedSymbol = futuresSettings.IsolatedFuturesSymbol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isMargin, isFutures, isIsolated, isolatedSymbol
|
||||
}
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
exchange2 "github.com/c9s/bbgo/pkg/exchange"
|
||||
"github.com/c9s/bbgo/pkg/exchange/batch"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -25,7 +26,7 @@ func (s *BacktestService) SyncKLineByInterval(ctx context.Context, exchange type
|
|||
log.Infof("synchronizing %s klines with interval %s: %s <=> %s", exchange.Name(), interval, startTime, endTime)
|
||||
|
||||
// TODO: use isFutures here
|
||||
_, _, isIsolated, isolatedSymbol := getExchangeAttributes(exchange)
|
||||
_, _, isIsolated, isolatedSymbol := exchange2.GetSessionAttributes(exchange)
|
||||
// override symbol if isolatedSymbol is not empty
|
||||
if isIsolated && len(isolatedSymbol) > 0 {
|
||||
symbol = isolatedSymbol
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange"
|
||||
"github.com/c9s/bbgo/pkg/exchange/batch"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -17,7 +18,7 @@ type DepositService struct {
|
|||
|
||||
// Sync syncs the withdraw records into db
|
||||
func (s *DepositService) Sync(ctx context.Context, ex types.Exchange, startTime time.Time) error {
|
||||
isMargin, isFutures, isIsolated, _ := getExchangeAttributes(ex)
|
||||
isMargin, isFutures, isIsolated, _ := exchange.GetSessionAttributes(ex)
|
||||
if isMargin || isFutures || isIsolated {
|
||||
// only works in spot
|
||||
return nil
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/jmoiron/sqlx"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
exchange2 "github.com/c9s/bbgo/pkg/exchange"
|
||||
"github.com/c9s/bbgo/pkg/exchange/batch"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -19,7 +20,7 @@ type OrderService struct {
|
|||
}
|
||||
|
||||
func (s *OrderService) Sync(ctx context.Context, exchange types.Exchange, symbol string, startTime time.Time) error {
|
||||
isMargin, isFutures, isIsolated, isolatedSymbol := getExchangeAttributes(exchange)
|
||||
isMargin, isFutures, isIsolated, isolatedSymbol := exchange2.GetSessionAttributes(exchange)
|
||||
// override symbol if isolatedSymbol is not empty
|
||||
if isIsolated && len(isolatedSymbol) > 0 {
|
||||
symbol = isolatedSymbol
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
exchange2 "github.com/c9s/bbgo/pkg/exchange"
|
||||
"github.com/c9s/bbgo/pkg/exchange/batch"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
|
@ -29,7 +30,7 @@ func (s *RewardService) Sync(ctx context.Context, exchange types.Exchange, start
|
|||
return ErrExchangeRewardServiceNotImplemented
|
||||
}
|
||||
|
||||
isMargin, isFutures, _, _ := getExchangeAttributes(exchange)
|
||||
isMargin, isFutures, _, _ := exchange2.GetSessionAttributes(exchange)
|
||||
if isMargin || isFutures {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
exchange2 "github.com/c9s/bbgo/pkg/exchange"
|
||||
"github.com/c9s/bbgo/pkg/exchange/batch"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -53,7 +54,7 @@ func NewTradeService(db *sqlx.DB) *TradeService {
|
|||
}
|
||||
|
||||
func (s *TradeService) Sync(ctx context.Context, exchange types.Exchange, symbol string, startTime time.Time) error {
|
||||
isMargin, isFutures, isIsolated, isolatedSymbol := getExchangeAttributes(exchange)
|
||||
isMargin, isFutures, isIsolated, isolatedSymbol := exchange2.GetSessionAttributes(exchange)
|
||||
// override symbol if isolatedSymbol is not empty
|
||||
if isIsolated && len(isolatedSymbol) > 0 {
|
||||
symbol = isolatedSymbol
|
||||
|
@ -412,28 +413,3 @@ func SelectLastTrades(ex types.ExchangeName, symbol string, isMargin, isFutures,
|
|||
Limit(limit)
|
||||
}
|
||||
|
||||
func getExchangeAttributes(exchange types.Exchange) (isMargin, isFutures, isIsolated bool, isolatedSymbol string) {
|
||||
if marginExchange, ok := exchange.(types.MarginExchange); ok {
|
||||
marginSettings := marginExchange.GetMarginSettings()
|
||||
isMargin = marginSettings.IsMargin
|
||||
if isMargin {
|
||||
isIsolated = marginSettings.IsIsolatedMargin
|
||||
if marginSettings.IsIsolatedMargin {
|
||||
isolatedSymbol = marginSettings.IsolatedMarginSymbol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if futuresExchange, ok := exchange.(types.FuturesExchange); ok {
|
||||
futuresSettings := futuresExchange.GetFuturesSettings()
|
||||
isFutures = futuresSettings.IsFutures
|
||||
if isFutures {
|
||||
isIsolated = futuresSettings.IsIsolatedFutures
|
||||
if futuresSettings.IsIsolatedFutures {
|
||||
isolatedSymbol = futuresSettings.IsolatedFuturesSymbol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isMargin, isFutures, isIsolated, isolatedSymbol
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange"
|
||||
"github.com/c9s/bbgo/pkg/exchange/batch"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -17,7 +18,7 @@ type WithdrawService struct {
|
|||
|
||||
// Sync syncs the withdrawal records into db
|
||||
func (s *WithdrawService) Sync(ctx context.Context, ex types.Exchange, startTime time.Time) error {
|
||||
isMargin, isFutures, isIsolated, _ := getExchangeAttributes(ex)
|
||||
isMargin, isFutures, isIsolated, _ := exchange.GetSessionAttributes(ex)
|
||||
if isMargin || isFutures || isIsolated {
|
||||
// only works in spot
|
||||
return nil
|
||||
|
|
|
@ -2,10 +2,12 @@ package pivotshort
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/indicator"
|
||||
"github.com/c9s/bbgo/pkg/risk"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -25,6 +27,7 @@ type BreakLow struct {
|
|||
// limit sell price = breakLowPrice * (1 + BounceRatio)
|
||||
BounceRatio fixedpoint.Value `json:"bounceRatio"`
|
||||
|
||||
Leverage fixedpoint.Value `json:"leverage"`
|
||||
Quantity fixedpoint.Value `json:"quantity"`
|
||||
StopEMARange fixedpoint.Value `json:"stopEMARange"`
|
||||
StopEMA *types.IntervalWindow `json:"stopEMA"`
|
||||
|
@ -63,8 +66,8 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
|
|||
|
||||
position := orderExecutor.Position()
|
||||
symbol := position.Symbol
|
||||
store, _ := session.MarketDataStore(symbol)
|
||||
standardIndicator, _ := session.StandardIndicatorSet(symbol)
|
||||
store, _ := session.MarketDataStore(s.Symbol)
|
||||
standardIndicator, _ := session.StandardIndicatorSet(s.Symbol)
|
||||
|
||||
s.lastLow = fixedpoint.Zero
|
||||
|
||||
|
@ -168,7 +171,15 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
|
|||
// graceful cancel all active orders
|
||||
_ = orderExecutor.GracefulCancel(ctx)
|
||||
|
||||
quantity := s.useQuantityOrBaseBalance(s.Quantity)
|
||||
quantity, err := useQuantityOrBaseBalance(s.session, s.Market, closePrice, s.Quantity, s.Leverage)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("quantity calculation error")
|
||||
}
|
||||
|
||||
if quantity.IsZero() {
|
||||
return
|
||||
}
|
||||
|
||||
if s.MarketOrder {
|
||||
bbgo.Notify("%s price %f breaks the previous low %f with ratio %f, submitting market sell to open a short position", symbol, kline.Close.Float64(), previousLow.Float64(), s.Ratio.Float64())
|
||||
_, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
|
@ -204,24 +215,70 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
|
|||
}
|
||||
}
|
||||
|
||||
func (s *BreakLow) useQuantityOrBaseBalance(quantity fixedpoint.Value) fixedpoint.Value {
|
||||
if s.session.Margin || s.session.IsolatedMargin || s.session.Futures || s.session.IsolatedFutures {
|
||||
return quantity
|
||||
func useQuantityOrBaseBalance(session *bbgo.ExchangeSession, market types.Market, price, quantity, leverage fixedpoint.Value) (fixedpoint.Value, error) {
|
||||
usingLeverage := session.Margin || session.IsolatedMargin || session.Futures || session.IsolatedFutures
|
||||
if usingLeverage {
|
||||
if !quantity.IsZero() {
|
||||
return quantity, nil
|
||||
}
|
||||
|
||||
balance, hasBalance := s.session.Account.Balance(s.Market.BaseCurrency)
|
||||
if leverage.IsZero() {
|
||||
leverage = fixedpoint.NewFromInt(3)
|
||||
}
|
||||
|
||||
// quantity is zero, we need to calculate the quantity
|
||||
baseBalance, _ := session.Account.Balance(market.BaseCurrency)
|
||||
quoteBalance, _ := session.Account.Balance(market.QuoteCurrency)
|
||||
|
||||
// calculate the quantity automatically
|
||||
if session.Margin || session.IsolatedMargin {
|
||||
baseBalanceValue := baseBalance.Total().Mul(price)
|
||||
accountValue := baseBalanceValue.Add(quoteBalance.Total())
|
||||
|
||||
if session.IsolatedMargin {
|
||||
originLeverage := leverage
|
||||
leverage = fixedpoint.Max(leverage, fixedpoint.NewFromInt(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
|
||||
maxPositionQuantity := risk.CalculateMaxPosition(price, accountValue, leverage)
|
||||
|
||||
log.Infof("margin leverage: calculated maxPositionQuantity=%f price=%f accountValue=%f %s leverage=%f",
|
||||
maxPositionQuantity.Float64(),
|
||||
price.Float64(),
|
||||
accountValue.Float64(),
|
||||
market.QuoteCurrency,
|
||||
leverage.Float64())
|
||||
|
||||
return maxPositionQuantity, 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 currency
|
||||
balance, hasBalance := session.Account.Balance(market.BaseCurrency)
|
||||
if hasBalance {
|
||||
if quantity.IsZero() {
|
||||
bbgo.Notify("sell quantity is not set, submitting sell with all base balance: %s", balance.Available.String())
|
||||
log.Warnf("sell quantity is not set, submitting sell with all base balance: %s", balance.Available.String())
|
||||
quantity = balance.Available
|
||||
} else {
|
||||
quantity = fixedpoint.Min(quantity, balance.Available)
|
||||
}
|
||||
}
|
||||
|
||||
if quantity.IsZero() {
|
||||
log.Errorf("quantity is zero, can not submit sell order, please check settings")
|
||||
}
|
||||
|
||||
return quantity
|
||||
return quantity, fmt.Errorf("quantity is zero, can not submit sell order, please check your settings")
|
||||
}
|
||||
|
|
|
@ -37,6 +37,10 @@ func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg
|
|||
s.session = session
|
||||
s.orderExecutor = orderExecutor
|
||||
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
||||
s.activeOrders.OnFilled(func(o types.Order) {
|
||||
// reset resistance price
|
||||
s.currentResistancePrice = fixedpoint.Zero
|
||||
})
|
||||
s.activeOrders.BindStream(session.UserDataStream)
|
||||
|
||||
if s.GroupDistance.IsZero() {
|
||||
|
@ -112,7 +116,10 @@ func (s *ResistanceShort) updateResistanceOrders(closePrice fixedpoint.Value) {
|
|||
ctx := context.Background()
|
||||
resistanceUpdated := s.updateCurrentResistancePrice(closePrice)
|
||||
if resistanceUpdated {
|
||||
bbgo.Notify("%s Found next resistance price at %f, updating resistance order...", s.Symbol, s.currentResistancePrice.Float64())
|
||||
bbgo.Notify("Found next %s resistance price at %f, updating resistance orders...", s.Symbol, s.currentResistancePrice.Float64())
|
||||
s.placeResistanceOrders(ctx, s.currentResistancePrice)
|
||||
} else if s.activeOrders.NumOfOrders() == 0 && !s.currentResistancePrice.IsZero() {
|
||||
bbgo.Notify("There is no %s resistance open order, re-placing resistance orders at %f...", s.Symbol, s.currentResistancePrice.Float64())
|
||||
s.placeResistanceOrders(ctx, s.currentResistancePrice)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,11 @@ type Strategy struct {
|
|||
// pivot interval and window
|
||||
types.IntervalWindow
|
||||
|
||||
Leverage fixedpoint.Value `json:"leverage"`
|
||||
Quantity fixedpoint.Value `json:"quantity"`
|
||||
|
||||
// persistence fields
|
||||
|
||||
Position *types.Position `persistence:"position"`
|
||||
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
|
||||
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
||||
|
@ -177,7 +181,6 @@ type Strategy struct {
|
|||
bbgo.StrategyController
|
||||
}
|
||||
|
||||
|
||||
func (s *Strategy) ID() string {
|
||||
return ID
|
||||
}
|
||||
|
@ -236,6 +239,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.TradeStats = types.NewTradeStats(s.Symbol)
|
||||
}
|
||||
|
||||
if s.Leverage.IsZero() {
|
||||
// the default leverage is 3x
|
||||
s.Leverage = fixedpoint.NewFromInt(3)
|
||||
}
|
||||
|
||||
// StrategyController
|
||||
s.Status = types.StrategyStatusRunning
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user