Merge pull request #833 from c9s/fix/pivotshort-quantity-cal

fix: strategy/pivotshort: fix margin quantity calculation
This commit is contained in:
Yo-An Lin 2022-07-21 12:58:09 +08:00 committed by GitHub
commit 8c61f68d1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 69 deletions

View File

@ -4,8 +4,8 @@ sessions:
exchange: binance
envVarPrefix: binance
margin: true
# isolatedMargin: true
# isolatedMarginSymbol: ETHUSDT
isolatedMargin: true
isolatedMarginSymbol: ETHUSDT
exchangeStrategies:
- on: binance
@ -20,6 +20,9 @@ exchangeStrategies:
quantity: 10.0
# when quantity is not given, leverage will be used.
# leverage: 10.0
# breakLow settings are used for shorting when the current price break the previous low
breakLow:
# ratio is how much the price breaks the previous low to trigger the short.

View File

@ -365,12 +365,18 @@ func (session *ExchangeSession) initSymbol(ctx context.Context, environ *Environ
orderStore.BindStream(session.UserDataStream)
session.orderStores[symbol] = orderStore
marketDataStore := NewMarketDataStore(symbol)
marketDataStore.BindStream(session.MarketDataStream)
session.marketDataStores[symbol] = marketDataStore
if _, ok := session.marketDataStores[symbol]; !ok {
marketDataStore := NewMarketDataStore(symbol)
marketDataStore.BindStream(session.MarketDataStream)
session.marketDataStores[symbol] = marketDataStore
}
standardIndicatorSet := NewStandardIndicatorSet(symbol, session.MarketDataStream, marketDataStore)
session.standardIndicatorSets[symbol] = standardIndicatorSet
marketDataStore := session.marketDataStores[symbol]
if _, ok := session.standardIndicatorSets[symbol]; !ok {
standardIndicatorSet := NewStandardIndicatorSet(symbol, session.MarketDataStream, marketDataStore)
session.standardIndicatorSets[symbol] = standardIndicatorSet
}
// used kline intervals by the given symbol
var klineSubscriptions = map[types.Interval]struct{}{}
@ -434,6 +440,14 @@ func (session *ExchangeSession) initSymbol(ctx context.Context, environ *Environ
func (session *ExchangeSession) StandardIndicatorSet(symbol string) (*StandardIndicatorSet, bool) {
set, ok := session.standardIndicatorSets[symbol]
if !ok {
if store, ok2 := session.MarketDataStore(symbol); ok2 {
set = NewStandardIndicatorSet(symbol, session.MarketDataStream, store)
session.standardIndicatorSets[symbol] = set
return set, true
}
}
return set, ok
}
@ -465,6 +479,12 @@ func (session *ExchangeSession) Positions() map[string]*types.Position {
// MarketDataStore returns the market data store of a symbol
func (session *ExchangeSession) MarketDataStore(symbol string) (s *MarketDataStore, ok bool) {
s, ok = session.marketDataStores[symbol]
if !ok {
s = NewMarketDataStore(symbol)
s.BindStream(session.MarketDataStream)
session.marketDataStores[symbol] = s
return s, true
}
return s, ok
}

View File

@ -211,52 +211,6 @@ func (trader *Trader) Subscribe() {
}
func (trader *Trader) RunSingleExchangeStrategy(ctx context.Context, strategy SingleExchangeStrategy, session *ExchangeSession, orderExecutor OrderExecutor) error {
rs := reflect.ValueOf(strategy)
// get the struct element
rs = rs.Elem()
if rs.Kind() != reflect.Struct {
return errors.New("strategy object is not a struct")
}
if err := trader.injectCommonServices(strategy); err != nil {
return err
}
if err := dynamic.InjectField(rs, "OrderExecutor", orderExecutor, false); err != nil {
return errors.Wrapf(err, "failed to inject OrderExecutor on %T", strategy)
}
if symbol, ok := dynamic.LookupSymbolField(rs); ok {
log.Infof("found symbol based strategy from %s", rs.Type())
market, ok := session.Market(symbol)
if !ok {
return fmt.Errorf("market of symbol %s not found", symbol)
}
indicatorSet, ok := session.StandardIndicatorSet(symbol)
if !ok {
return fmt.Errorf("standardIndicatorSet of symbol %s not found", symbol)
}
store, ok := session.MarketDataStore(symbol)
if !ok {
return fmt.Errorf("marketDataStore of symbol %s not found", symbol)
}
if err := dynamic.ParseStructAndInject(strategy,
market,
indicatorSet,
store,
session,
session.OrderExecutor,
); err != nil {
return errors.Wrapf(err, "failed to inject object into %T", strategy)
}
}
if v, ok := strategy.(StrategyValidator); ok {
if err := v.Validate(); err != nil {
return fmt.Errorf("failed to validate the config: %w", err)
@ -307,12 +261,87 @@ func (trader *Trader) RunAllSingleExchangeStrategy(ctx context.Context) error {
return nil
}
func (trader *Trader) injectFields() error {
// load and run Session strategies
for sessionName, strategies := range trader.exchangeStrategies {
var session = trader.environment.sessions[sessionName]
var orderExecutor = trader.getSessionOrderExecutor(sessionName)
for _, strategy := range strategies {
rs := reflect.ValueOf(strategy)
// get the struct element
rs = rs.Elem()
if rs.Kind() != reflect.Struct {
return errors.New("strategy object is not a struct")
}
if err := trader.injectCommonServices(strategy); err != nil {
return err
}
if err := dynamic.InjectField(rs, "OrderExecutor", orderExecutor, false); err != nil {
return errors.Wrapf(err, "failed to inject OrderExecutor on %T", strategy)
}
if symbol, ok := dynamic.LookupSymbolField(rs); ok {
log.Infof("found symbol based strategy from %s", rs.Type())
market, ok := session.Market(symbol)
if !ok {
return fmt.Errorf("market of symbol %s not found", symbol)
}
indicatorSet, ok := session.StandardIndicatorSet(symbol)
if !ok {
return fmt.Errorf("standardIndicatorSet of symbol %s not found", symbol)
}
store, ok := session.MarketDataStore(symbol)
if !ok {
return fmt.Errorf("marketDataStore of symbol %s not found", symbol)
}
if err := dynamic.ParseStructAndInject(strategy,
market,
session,
session.OrderExecutor,
indicatorSet,
store,
); err != nil {
return errors.Wrapf(err, "failed to inject object into %T", strategy)
}
}
}
}
for _, strategy := range trader.crossExchangeStrategies {
rs := reflect.ValueOf(strategy)
// get the struct element from the struct pointer
rs = rs.Elem()
if rs.Kind() != reflect.Struct {
continue
}
if err := trader.injectCommonServices(strategy); err != nil {
return err
}
}
return nil
}
func (trader *Trader) Run(ctx context.Context) error {
// before we start the interaction,
// register the core interaction, because we can only get the strategies in this scope
// trader.environment.Connect will call interact.Start
interact.AddCustomInteraction(NewCoreInteraction(trader.environment, trader))
if err := trader.injectFields(); err != nil {
return err
}
trader.Subscribe()
if err := trader.environment.Start(ctx); err != nil {
@ -333,18 +362,6 @@ func (trader *Trader) Run(ctx context.Context) error {
}
for _, strategy := range trader.crossExchangeStrategies {
rs := reflect.ValueOf(strategy)
// get the struct element from the struct pointer
rs = rs.Elem()
if rs.Kind() != reflect.Struct {
continue
}
if err := trader.injectCommonServices(strategy); err != nil {
return err
}
if err := strategy.CrossRun(ctx, router, trader.environment.sessions); err != nil {
return err
}

View File

@ -96,11 +96,28 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
}
if lastLow.Compare(s.lastLow) != 0 {
bbgo.Notify("%s new pivot low detected: %f", s.Symbol, s.pivot.LastLow())
bbgo.Notify("%s found new pivot low: %f", s.Symbol, s.pivot.LastLow())
}
s.lastLow = lastLow
s.pivotLowPrices = append(s.pivotLowPrices, s.lastLow)
log.Infof("pilot calculation for max position: last low = %f, quantity = %f, leverage = %f",
s.lastLow.Float64(),
s.Quantity.Float64(),
s.Leverage.Float64())
quantity, err := useQuantityOrBaseBalance(s.session, s.Market, s.lastLow, s.Quantity, s.Leverage)
if err != nil {
log.WithError(err).Errorf("quantity calculation error")
}
if quantity.IsZero() {
log.WithError(err).Errorf("quantity is zero, can not submit order")
return
}
bbgo.Notify("%s %f quantity will be used for shorting", s.Symbol, quantity.Float64())
})
session.MarketDataStream.OnKLineClosed(types.KLineWith(symbol, s.Interval, func(kline types.KLine) {
@ -110,7 +127,7 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
}
if lastLow.Compare(s.lastLow) != 0 {
bbgo.Notify("%s new pivot low detected: %f %s", s.Symbol, s.pivot.LastLow())
bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivot.LastLow())
}
s.lastLow = lastLow
@ -237,10 +254,14 @@ func useQuantityOrBaseBalance(session *bbgo.ExchangeSession, market types.Market
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.Total().Mul(price)
accountValue := baseBalanceValue.Add(quoteBalance.Total())
baseBalanceValue := baseBalance.Net().Mul(price)
accountValue := baseBalanceValue.Add(quoteBalance.Net())
log.Infof("calculated account value %f %s", accountValue.Float64(), market.QuoteCurrency)
if session.IsolatedMargin {
originLeverage := leverage