diff --git a/config/pivotshort.yaml b/config/pivotshort.yaml index 869b1fb63..a7512ed53 100644 --- a/config/pivotshort.yaml +++ b/config/pivotshort.yaml @@ -47,22 +47,22 @@ exchangeStrategies: resistanceShort: enabled: true - interval: 1h - window: 8 + interval: 5m + window: 80 quantity: 10.0 # minDistance is used to ignore the place that is too near to the current price - minDistance: 3% + minDistance: 5% groupDistance: 1% # ratio is the ratio of the resistance price, - # higher the ratio, lower the price - # first_layer_price = resistance_price * (1 - ratio) - # second_layer_price = (resistance_price * (1 - ratio)) * (2 * layerSpread) - ratio: 0% - numOfLayers: 1 - layerSpread: 0.1% + # higher the ratio, higher the sell price + # first_layer_price = resistance_price * (1 + ratio) + # second_layer_price = (resistance_price * (1 + ratio)) * (2 * layerSpread) + ratio: 1.2% + numOfLayers: 3 + layerSpread: 0.4% exits: # (0) roiStopLoss is the stop loss percentage of the position ROI (currently the price change) diff --git a/pkg/strategy/pivotshort/math.go b/pkg/strategy/pivotshort/math.go new file mode 100644 index 000000000..9f5df82f2 --- /dev/null +++ b/pkg/strategy/pivotshort/math.go @@ -0,0 +1,66 @@ +package pivotshort + +import "sort" + +func lower(arr []float64, x float64) []float64 { + sort.Float64s(arr) + + var rst []float64 + for _, a := range arr { + // filter prices that are lower than the current closed price + if a > x { + continue + } + + rst = append(rst, a) + } + + return rst +} + +func higher(arr []float64, x float64) []float64 { + sort.Float64s(arr) + + var rst []float64 + for _, a := range arr { + // filter prices that are lower than the current closed price + if a < x { + continue + } + rst = append(rst, a) + } + + return rst +} + +func group(arr []float64, minDistance float64) []float64 { + if len(arr) == 0 { + return nil + } + + var groups []float64 + var grp = []float64{arr[0]} + for _, price := range arr { + avg := average(grp) + if (price / avg) > (1.0 + minDistance) { + groups = append(groups, avg) + grp = []float64{price} + } else { + grp = append(grp, price) + } + } + + if len(grp) > 0 { + groups = append(groups, average(grp)) + } + + return groups +} + +func average(arr []float64) float64 { + s := 0.0 + for _, a := range arr { + s += a + } + return s / float64(len(arr)) +} diff --git a/pkg/strategy/pivotshort/resistance.go b/pkg/strategy/pivotshort/resistance.go index 200152f1c..6c8aa21d6 100644 --- a/pkg/strategy/pivotshort/resistance.go +++ b/pkg/strategy/pivotshort/resistance.go @@ -2,7 +2,6 @@ package pivotshort import ( "context" - "sort" "github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/fixedpoint" @@ -98,6 +97,9 @@ func (s *ResistanceShort) updateNextResistancePrice(closePrice fixedpoint.Value) return true } + // if the current sell price is out-dated + // or + // the next resistance is lower than the current one. currentSellPrice := s.currentResistancePrice.Mul(one.Add(s.Ratio)) if closePrice.Compare(currentSellPrice) > 0 || nextResistancePrice.Compare(currentSellPrice) < 0 { @@ -184,69 +186,6 @@ func findPossibleSupportPrices(closePrice float64, minDistance float64, lows []f return group(lower(lows, closePrice), minDistance) } -func lower(arr []float64, x float64) []float64 { - sort.Float64s(arr) - - var rst []float64 - for _, a := range arr { - // filter prices that are lower than the current closed price - if a > x { - continue - } - - rst = append(rst, a) - } - - return rst -} - -func higher(arr []float64, x float64) []float64 { - sort.Float64s(arr) - - var rst []float64 - for _, a := range arr { - // filter prices that are lower than the current closed price - if a < x { - continue - } - rst = append(rst, a) - } - - return rst -} - -func group(arr []float64, minDistance float64) []float64 { - if len(arr) == 0 { - return nil - } - - var groups []float64 - var grp = []float64{arr[0]} - for _, price := range arr { - avg := average(grp) - if (price / avg) > (1.0 + minDistance) { - groups = append(groups, avg) - grp = []float64{price} - } else { - grp = append(grp, price) - } - } - - if len(grp) > 0 { - groups = append(groups, average(grp)) - } - - return groups -} - func findPossibleResistancePrices(closePrice float64, minDistance float64, lows []float64) []float64 { return group(higher(lows, closePrice), minDistance) } - -func average(arr []float64) float64 { - s := 0.0 - for _, a := range arr { - s += a - } - return s / float64(len(arr)) -} diff --git a/pkg/strategy/pivotshort/strategy.go b/pkg/strategy/pivotshort/strategy.go index 74cef7103..1fc671c92 100644 --- a/pkg/strategy/pivotshort/strategy.go +++ b/pkg/strategy/pivotshort/strategy.go @@ -35,16 +35,42 @@ type SupportTakeProfit struct { types.IntervalWindow Ratio fixedpoint.Value `json:"ratio"` - pivot *indicator.Pivot - orderExecutor *bbgo.GeneralOrderExecutor - session *bbgo.ExchangeSession - activeOrders *bbgo.ActiveOrderBook + pivot *indicator.Pivot + orderExecutor *bbgo.GeneralOrderExecutor + session *bbgo.ExchangeSession + activeOrders *bbgo.ActiveOrderBook + currentSupportPrice fixedpoint.Value } func (s *SupportTakeProfit) Subscribe(session *bbgo.ExchangeSession) { session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) } +func (s *SupportTakeProfit) updateSupportPrice(closePrice fixedpoint.Value) bool { + supportPrices := findPossibleSupportPrices(closePrice.Float64(), 0.05, s.pivot.Lows) + + if len(supportPrices) == 0 { + return false + } + + // nextSupportPrice are sorted in decreasing order + nextSupportPrice := fixedpoint.NewFromFloat(supportPrices[0]) + currentBuyPrice := s.currentSupportPrice.Mul(one.Add(s.Ratio)) + + if s.currentSupportPrice.IsZero() { + s.currentSupportPrice = nextSupportPrice + return true + } + + // the close price is already lower than the support price, than we should update + if closePrice.Compare(currentBuyPrice) < 0 || nextSupportPrice.Compare(s.currentSupportPrice) > 0 { + s.currentSupportPrice = nextSupportPrice + return true + } + + return false +} + func (s *SupportTakeProfit) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) { s.session = session s.orderExecutor = orderExecutor @@ -58,27 +84,23 @@ func (s *SupportTakeProfit) Bind(session *bbgo.ExchangeSession, orderExecutor *b preloadPivot(s.pivot, store) session.UserDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { - supportPrices := findPossibleSupportPrices(kline.Close.Float64(), 0.1, s.pivot.Lows) - // supportPrices are sorted in decreasing order - if len(supportPrices) == 0 { - log.Infof("support prices not found") - return - } - if !position.IsOpened(kline.Close) { return } - nextSupport := fixedpoint.NewFromFloat(supportPrices[0]) - buyPrice := nextSupport.Mul(one.Add(s.Ratio)) - quantity := position.GetQuantity() + if !s.updateSupportPrice(kline.Close) { + return + } + buyPrice := s.currentSupportPrice.Mul(one.Add(s.Ratio)) + quantity := position.GetQuantity() ctx := context.Background() if err := orderExecutor.GracefulCancelActiveOrderBook(ctx, s.activeOrders); err != nil { log.WithError(err).Errorf("cancel order failed") } + bbgo.Notify("placing %s take profit order at price %f", s.Symbol, buyPrice.Float64()) createdOrders, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{ Symbol: symbol, Type: types.OrderTypeLimitMaker, @@ -112,7 +134,7 @@ type Strategy struct { // ResistanceShort is one of the entry method ResistanceShort *ResistanceShort `json:"resistanceShort"` - SupportTakeProfit *SupportTakeProfit `json:"supportTakeProfit"` + SupportTakeProfit []SupportTakeProfit `json:"supportTakeProfit"` ExitMethods bbgo.ExitMethodSet `json:"exits"` @@ -141,9 +163,9 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { s.BreakLow.Subscribe(session) } - if s.SupportTakeProfit != nil { - dynamic.InheritStructValues(s.SupportTakeProfit, s) - s.SupportTakeProfit.Subscribe(session) + for i := range s.SupportTakeProfit { + dynamic.InheritStructValues(&s.SupportTakeProfit[i], s) + s.SupportTakeProfit[i].Subscribe(session) } if !bbgo.IsBackTesting {