pivotshort: fix support take profit method

This commit is contained in:
c9s 2022-07-03 17:13:01 +08:00
parent 74cac6e977
commit 278fbb7b51
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
4 changed files with 118 additions and 91 deletions

View File

@ -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)

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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 {