diff --git a/config/funding.yaml b/config/funding.yaml index 0b9621d5f..0fcd433ed 100644 --- a/config/funding.yaml +++ b/config/funding.yaml @@ -26,11 +26,13 @@ exchangeStrategies: - on: binance funding: symbol: ETHUSDT + quantity: 0.0001 fundingRate: high: 0.01% supportDetection: - - interval: 5m + - interval: 1m movingAverageType: EMA - movingAverageInterval: 1h - movingAverageWindow: 99 + movingAverageIntervalWindow: + interval: 15m + window: 60 minVolume: 8_000 diff --git a/pkg/strategy/funding/strategy.go b/pkg/strategy/funding/strategy.go index 752e36094..7e12d81c9 100644 --- a/pkg/strategy/funding/strategy.go +++ b/pkg/strategy/funding/strategy.go @@ -4,13 +4,11 @@ import ( "context" "errors" "fmt" - "math" - "strings" - "time" - "github.com/c9s/bbgo/pkg/exchange/binance" "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/sirupsen/logrus" + "math" + "strings" "github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/types" @@ -29,12 +27,12 @@ func init() { type Strategy struct { *bbgo.Notifiability - // These fields will be filled from the config file (it translates YAML to JSON) - Symbol string `json:"symbol"` - Market types.Market `json:"-"` - Quantity fixedpoint.Value `json:"quantity,omitempty"` - MaxPosition fixedpoint.Value `json:"maxPosition"` + Symbol string `json:"symbol"` + Market types.Market `json:"-"` + Quantity fixedpoint.Value `json:"quantity,omitempty"` + MaxExposurePosition fixedpoint.Value `json:"maxExposurePosition"` + //Interval types.Interval `json:"interval"` FundingRate *struct { High fixedpoint.Value `json:"high"` @@ -44,7 +42,6 @@ type Strategy struct { SupportDetection []struct { Interval types.Interval `json:"interval"` - // MovingAverageType is the moving average indicator type that we want to use, // it could be SMA or EWMA MovingAverageType string `json:"movingAverageType"` @@ -52,11 +49,13 @@ type Strategy struct { // MovingAverageInterval is the interval of k-lines for the moving average indicator to calculate, // it could be "1m", "5m", "1h" and so on. note that, the moving averages are calculated from // the k-line data we subscribed - MovingAverageInterval types.Interval `json:"movingAverageInterval"` + //MovingAverageInterval types.Interval `json:"movingAverageInterval"` + // + //// MovingAverageWindow is the number of the window size of the moving average indicator. + //// The number of k-lines in the window. generally used window sizes are 7, 25 and 99 in the TradingView. + //MovingAverageWindow int `json:"movingAverageWindow"` - // MovingAverageWindow is the number of the window size of the moving average indicator. - // The number of k-lines in the window. generally used window sizes are 7, 25 and 99 in the TradingView. - MovingAverageWindow int `json:"movingAverageWindow"` + MovingAverageIntervalWindow types.IntervalWindow `json:"movingAverageIntervalWindow"` MinVolume fixedpoint.Value `json:"minVolume"` @@ -70,13 +69,17 @@ func (s *Strategy) ID() string { func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { // session.Subscribe(types.BookChannel, s.Symbol, types.SubscribeOptions{}) + + //session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{ + // Interval: string(s.Interval), + //}) + for _, detection := range s.SupportDetection { session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{ Interval: string(detection.Interval), }) - session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{ - Interval: string(detection.MovingAverageInterval), + Interval: string(detection.MovingAverageIntervalWindow.Interval), }) } } @@ -89,127 +92,68 @@ func (s *Strategy) Validate() error { return nil } -func (s *Strategy) listenToFundingRate(ctx context.Context, exchange *binance.Exchange) { - var previousIndex, fundingRate24HoursLowIndex *types.PremiumIndex - - fundingRateTicker := time.NewTicker(1 * time.Hour) - defer fundingRateTicker.Stop() - for { - select { - - case <-ctx.Done(): - return - - case <-fundingRateTicker.C: - index, err := exchange.QueryPremiumIndex(ctx, s.Symbol) - if err != nil { - log.WithError(err).Error("can not query last funding rate") - continue - } - - fundingRate := index.LastFundingRate - - if fundingRate >= s.FundingRate.High { - s.Notifiability.Notify("%s funding rate %s is too high! threshold %s", - s.Symbol, - fundingRate.Percentage(), - s.FundingRate.High.Percentage(), - ) - } else { - if previousIndex != nil { - if s.FundingRate.DiffThreshold == 0 { - // 0.6% - s.FundingRate.DiffThreshold = fixedpoint.NewFromFloat(0.006 * 0.01) - } - - diff := fundingRate - previousIndex.LastFundingRate - if diff.Abs() > s.FundingRate.DiffThreshold { - s.Notifiability.Notify("%s funding rate changed %s, current funding rate %s", - s.Symbol, - diff.SignedPercentage(), - fundingRate.Percentage(), - ) - } - } - } - - previousIndex = index - if fundingRate24HoursLowIndex != nil { - if fundingRate24HoursLowIndex.Time.Before(time.Now().Add(24 * time.Hour)) { - fundingRate24HoursLowIndex = index - } - if fundingRate < fundingRate24HoursLowIndex.LastFundingRate { - fundingRate24HoursLowIndex = index - } - } else { - fundingRate24HoursLowIndex = index - } - } - } -} - func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + standardIndicatorSet, ok := session.StandardIndicatorSet(s.Symbol) if !ok { return fmt.Errorf("standardIndicatorSet is nil, symbol %s", s.Symbol) } - - binanceExchange, ok := session.Exchange.(*binance.Exchange) - if !ok { - log.Error("exchange does not support funding rate api") + //binanceExchange, ok := session.Exchange.(*binance.Exchange) + //if !ok { + // log.Error("exchange failed") + //} + if !session.Futures { + log.Error("futures not enabled in config for this strategy") + return nil } - binanceExchange.UseFutures() + //if s.FundingRate != nil { // go s.listenToFundingRate(ctx, binanceExchange) //} + premiumIndex, err := session.Exchange.(*binance.Exchange).QueryPremiumIndex(ctx, s.Symbol) + if err != nil { + log.Error("exchange does not support funding rate api") + } + + var ma types.Float64Indicator + for _, detection := range s.SupportDetection { + + switch strings.ToLower(detection.MovingAverageType) { + case "sma": + ma = standardIndicatorSet.SMA(types.IntervalWindow{ + Interval: detection.MovingAverageIntervalWindow.Interval, + Window: detection.MovingAverageIntervalWindow.Window, + }) + case "ema", "ewma": + ma = standardIndicatorSet.EWMA(types.IntervalWindow{ + Interval: detection.MovingAverageIntervalWindow.Interval, + Window: detection.MovingAverageIntervalWindow.Window, + }) + default: + ma = standardIndicatorSet.EWMA(types.IntervalWindow{ + Interval: detection.MovingAverageIntervalWindow.Interval, + Window: detection.MovingAverageIntervalWindow.Window, + }) + } + + } session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { // skip k-lines from other symbols if kline.Symbol != s.Symbol { return } - log.Infof(s.Symbol) for _, detection := range s.SupportDetection { - if kline.Interval != detection.Interval { - continue - } + var lastMA = ma.Last() closePriceF := kline.GetClose() closePrice := fixedpoint.NewFromFloat(closePriceF) - - var ma types.Float64Indicator - - switch strings.ToLower(detection.MovingAverageType) { - case "sma": - ma = standardIndicatorSet.SMA(types.IntervalWindow{ - Interval: detection.MovingAverageInterval, - Window: detection.MovingAverageWindow, - }) - case "ema", "ewma": - ma = standardIndicatorSet.EWMA(types.IntervalWindow{ - Interval: detection.MovingAverageInterval, - Window: detection.MovingAverageWindow, - }) - default: - ma = standardIndicatorSet.EWMA(types.IntervalWindow{ - Interval: detection.MovingAverageInterval, - Window: detection.MovingAverageWindow, - }) - } - - var lastMA = ma.Last() - // skip if the closed price is under the moving average if closePrice.Float64() < lastMA { - log.Infof("skip %s support closed price %f > last ma %f", s.Symbol, closePrice.Float64(), lastMA) + log.Infof("skip %s closed price %f < last ma %f", s.Symbol, closePrice.Float64(), lastMA) return } - premiumIndex, err := binanceExchange.QueryPremiumIndex(ctx, s.Symbol) - if err != nil { - log.Error("exchange does not support funding rate api") - } - fundingRate := premiumIndex.LastFundingRate if fundingRate >= s.FundingRate.High { @@ -219,6 +163,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.FundingRate.High.Percentage(), ) } else { + log.Infof("skip funding rate is too low") return } @@ -239,8 +184,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return } - if baseBalance.Available > 0 && baseBalance.Total() < s.MaxPosition { - log.Infof("start to short position, selling futures..") + if baseBalance.Available > 0 && baseBalance.Total() < s.MaxExposurePosition { + log.Infof("opening a short position") _, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{ Symbol: kline.Symbol, Side: types.SideTypeSell,