pivotshort: fix supportTakeProfit binding

This commit is contained in:
c9s 2022-07-04 02:20:15 +08:00
parent 81f9639c85
commit 3a37154737
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
3 changed files with 94 additions and 67 deletions

View File

@ -87,24 +87,19 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
} }
previousLow := s.pivotLowPrices[len(s.pivotLowPrices)-1] previousLow := s.pivotLowPrices[len(s.pivotLowPrices)-1]
// truncate the pivot low prices
if len(s.pivotLowPrices) > 10 {
s.pivotLowPrices = s.pivotLowPrices[len(s.pivotLowPrices)-10:]
}
ratio := fixedpoint.One.Add(s.Ratio) ratio := fixedpoint.One.Add(s.Ratio)
breakPrice := previousLow.Mul(ratio) breakPrice := previousLow.Mul(ratio)
openPrice := kline.Open openPrice := kline.Open
closePrice := kline.Close closePrice := kline.Close
// if previous low is not break, skip // if the previous low is not break, or the kline is not strong enough to break it, skip
if closePrice.Compare(breakPrice) >= 0 { if closePrice.Compare(breakPrice) >= 0 {
return return
} }
// we need the price cross the break line or we do nothing // we need the price cross the break line, or we do nothing:
// open > break price > close price
if !(openPrice.Compare(breakPrice) > 0 && closePrice.Compare(breakPrice) < 0) { if !(openPrice.Compare(breakPrice) > 0 && closePrice.Compare(breakPrice) < 0) {
return return
} }

View File

@ -60,7 +60,6 @@ func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
position := s.orderExecutor.Position() position := s.orderExecutor.Position()
if position.IsOpened(kline.Close) { if position.IsOpened(kline.Close) {
log.Infof("position is already opened, skip placing resistance orders")
return return
} }
@ -182,10 +181,10 @@ func (s *ResistanceShort) placeResistanceOrders(ctx context.Context, resistanceP
s.activeOrders.Add(createdOrders...) s.activeOrders.Add(createdOrders...)
} }
func findPossibleSupportPrices(closePrice float64, minDistance float64, lows []float64) []float64 { func findPossibleSupportPrices(closePrice float64, groupDistance float64, lows []float64) []float64 {
return group(lower(lows, closePrice), minDistance) return group(lower(lows, closePrice), groupDistance)
} }
func findPossibleResistancePrices(closePrice float64, minDistance float64, lows []float64) []float64 { func findPossibleResistancePrices(closePrice float64, groupDistance float64, lows []float64) []float64 {
return group(higher(lows, closePrice), minDistance) return group(higher(lows, closePrice), groupDistance)
} }

View File

@ -33,6 +33,7 @@ type IntervalWindowSetting struct {
type SupportTakeProfit struct { type SupportTakeProfit struct {
Symbol string Symbol string
types.IntervalWindow types.IntervalWindow
Ratio fixedpoint.Value `json:"ratio"` Ratio fixedpoint.Value `json:"ratio"`
pivot *indicator.Pivot pivot *indicator.Pivot
@ -40,20 +41,97 @@ type SupportTakeProfit struct {
session *bbgo.ExchangeSession session *bbgo.ExchangeSession
activeOrders *bbgo.ActiveOrderBook activeOrders *bbgo.ActiveOrderBook
currentSupportPrice fixedpoint.Value currentSupportPrice fixedpoint.Value
triggeredPrices []fixedpoint.Value
} }
func (s *SupportTakeProfit) Subscribe(session *bbgo.ExchangeSession) { func (s *SupportTakeProfit) Subscribe(session *bbgo.ExchangeSession) {
log.Infof("[supportTakeProfit] Subscribe(%s, %s)", s.Symbol, s.Interval)
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
} }
func (s *SupportTakeProfit) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
log.Infof("[supportTakeProfit] Bind(%s, %s)", s.Symbol, s.Interval)
s.session = session
s.orderExecutor = orderExecutor
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
session.UserDataStream.OnOrderUpdate(func(order types.Order) {
if s.activeOrders.Exists(order) {
if !s.currentSupportPrice.IsZero() {
s.triggeredPrices = append(s.triggeredPrices, s.currentSupportPrice)
}
}
})
s.activeOrders.BindStream(session.UserDataStream)
position := orderExecutor.Position()
symbol := position.Symbol
store, _ := session.MarketDataStore(symbol)
s.pivot = &indicator.Pivot{IntervalWindow: s.IntervalWindow}
s.pivot.Bind(store)
preloadPivot(s.pivot, store)
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
if !s.updateSupportPrice(kline.Close) {
return
}
if !position.IsOpened(kline.Close) {
log.Infof("position is not opened, skip updating support take profit order")
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,
Side: types.SideTypeBuy,
Price: buyPrice,
Quantity: quantity,
Tag: "supportTakeProfit",
})
if err != nil {
log.WithError(err).Errorf("can not submit orders: %+v", createdOrders)
}
s.activeOrders.Add(createdOrders...)
}))
}
func (s *SupportTakeProfit) updateSupportPrice(closePrice fixedpoint.Value) bool { func (s *SupportTakeProfit) updateSupportPrice(closePrice fixedpoint.Value) bool {
supportPrices := findPossibleSupportPrices(closePrice.Float64(), 0.05, s.pivot.Lows) log.Infof("[supportTakeProfit] lows: %v", s.pivot.Lows)
groupDistance := 0.01
minDistance := 0.05
supportPrices := findPossibleSupportPrices(closePrice.Float64()*(1.0-minDistance), groupDistance, s.pivot.Lows)
if len(supportPrices) == 0 { if len(supportPrices) == 0 {
return false return false
} }
// nextSupportPrice are sorted in decreasing order log.Infof("[supportTakeProfit] found possible support prices: %v", supportPrices)
nextSupportPrice := fixedpoint.NewFromFloat(supportPrices[0])
// nextSupportPrice are sorted in increasing order
nextSupportPrice := fixedpoint.NewFromFloat(supportPrices[len(supportPrices)-1])
// it's price that we have been used to take profit
for _, p := range s.triggeredPrices {
var l = p.Mul(one.Sub(fixedpoint.NewFromFloat(0.01)))
var h = p.Mul(one.Add(fixedpoint.NewFromFloat(0.01)))
if p.Compare(l) > 0 && p.Compare(h) < 0 {
return false
}
}
currentBuyPrice := s.currentSupportPrice.Mul(one.Add(s.Ratio)) currentBuyPrice := s.currentSupportPrice.Mul(one.Add(s.Ratio))
if s.currentSupportPrice.IsZero() { if s.currentSupportPrice.IsZero() {
@ -72,52 +150,6 @@ func (s *SupportTakeProfit) updateSupportPrice(closePrice fixedpoint.Value) bool
return false return false
} }
func (s *SupportTakeProfit) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
s.session = session
s.orderExecutor = orderExecutor
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
position := orderExecutor.Position()
symbol := position.Symbol
store, _ := session.MarketDataStore(symbol)
s.pivot = &indicator.Pivot{IntervalWindow: s.IntervalWindow}
s.pivot.Bind(store)
preloadPivot(s.pivot, store)
session.UserDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
if !position.IsOpened(kline.Close) {
return
}
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,
Price: buyPrice,
Quantity: quantity,
Tag: "supportTakeProfit",
})
if err != nil {
log.WithError(err).Errorf("can not submit orders: %+v", createdOrders)
}
s.activeOrders.Add(createdOrders...)
}))
}
type Strategy struct { type Strategy struct {
Environment *bbgo.Environment Environment *bbgo.Environment
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
@ -137,7 +169,7 @@ type Strategy struct {
// ResistanceShort is one of the entry method // ResistanceShort is one of the entry method
ResistanceShort *ResistanceShort `json:"resistanceShort"` ResistanceShort *ResistanceShort `json:"resistanceShort"`
SupportTakeProfit []SupportTakeProfit `json:"supportTakeProfit"` SupportTakeProfit []*SupportTakeProfit `json:"supportTakeProfit"`
ExitMethods bbgo.ExitMethodSet `json:"exits"` ExitMethods bbgo.ExitMethodSet `json:"exits"`
@ -167,8 +199,9 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
} }
for i := range s.SupportTakeProfit { for i := range s.SupportTakeProfit {
dynamic.InheritStructValues(&s.SupportTakeProfit[i], s) m := s.SupportTakeProfit[i]
s.SupportTakeProfit[i].Subscribe(session) dynamic.InheritStructValues(m, s)
m.Subscribe(session)
} }
if !bbgo.IsBackTesting { if !bbgo.IsBackTesting {
@ -243,8 +276,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.BreakLow.Bind(session, s.orderExecutor) s.BreakLow.Bind(session, s.orderExecutor)
} }
for _, m := range s.SupportTakeProfit { for i := range s.SupportTakeProfit {
m.Bind(session, s.orderExecutor) s.SupportTakeProfit[i].Bind(session, s.orderExecutor)
} }
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) { bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {