mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Merge pull request #904 from c9s/strategy/pivotshort-failed-break-high
strategy/pivotshort: add failed break high
This commit is contained in:
commit
3aa9636d98
|
@ -24,18 +24,23 @@ type StandardIndicatorSet struct {
|
||||||
// Standard indicators
|
// Standard indicators
|
||||||
// interval -> window
|
// interval -> window
|
||||||
iwbIndicators map[types.IntervalWindowBandWidth]*indicator.BOLL
|
iwbIndicators map[types.IntervalWindowBandWidth]*indicator.BOLL
|
||||||
iwIndicators map[types.IntervalWindow]indicator.KLinePusher
|
iwIndicators map[indicatorKey]indicator.KLinePusher
|
||||||
|
|
||||||
stream types.Stream
|
stream types.Stream
|
||||||
store *MarketDataStore
|
store *MarketDataStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type indicatorKey struct {
|
||||||
|
iw types.IntervalWindow
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
func NewStandardIndicatorSet(symbol string, stream types.Stream, store *MarketDataStore) *StandardIndicatorSet {
|
func NewStandardIndicatorSet(symbol string, stream types.Stream, store *MarketDataStore) *StandardIndicatorSet {
|
||||||
return &StandardIndicatorSet{
|
return &StandardIndicatorSet{
|
||||||
Symbol: symbol,
|
Symbol: symbol,
|
||||||
store: store,
|
store: store,
|
||||||
stream: stream,
|
stream: stream,
|
||||||
iwIndicators: make(map[types.IntervalWindow]indicator.KLinePusher),
|
iwIndicators: make(map[indicatorKey]indicator.KLinePusher),
|
||||||
iwbIndicators: make(map[types.IntervalWindowBandWidth]*indicator.BOLL),
|
iwbIndicators: make(map[types.IntervalWindowBandWidth]*indicator.BOLL),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,69 +55,77 @@ func (s *StandardIndicatorSet) initAndBind(inc indicator.KLinePusher, interval t
|
||||||
s.stream.OnKLineClosed(types.KLineWith(s.Symbol, interval, inc.PushK))
|
s.stream.OnKLineClosed(types.KLineWith(s.Symbol, interval, inc.PushK))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StandardIndicatorSet) allocateSimpleIndicator(t indicator.KLinePusher, iw types.IntervalWindow) indicator.KLinePusher {
|
func (s *StandardIndicatorSet) allocateSimpleIndicator(t indicator.KLinePusher, iw types.IntervalWindow, id string) indicator.KLinePusher {
|
||||||
inc, ok := s.iwIndicators[iw]
|
k := indicatorKey{
|
||||||
|
iw: iw,
|
||||||
|
id: id,
|
||||||
|
}
|
||||||
|
inc, ok := s.iwIndicators[k]
|
||||||
if ok {
|
if ok {
|
||||||
return inc
|
return inc
|
||||||
}
|
}
|
||||||
|
|
||||||
inc = t
|
inc = t
|
||||||
s.initAndBind(inc, iw.Interval)
|
s.initAndBind(inc, iw.Interval)
|
||||||
s.iwIndicators[iw] = inc
|
s.iwIndicators[k] = inc
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// SMA is a helper function that returns the simple moving average indicator of the given interval and the window size.
|
// SMA is a helper function that returns the simple moving average indicator of the given interval and the window size.
|
||||||
func (s *StandardIndicatorSet) SMA(iw types.IntervalWindow) *indicator.SMA {
|
func (s *StandardIndicatorSet) SMA(iw types.IntervalWindow) *indicator.SMA {
|
||||||
inc := s.allocateSimpleIndicator(&indicator.SMA{IntervalWindow: iw}, iw)
|
inc := s.allocateSimpleIndicator(&indicator.SMA{IntervalWindow: iw}, iw, "sma")
|
||||||
return inc.(*indicator.SMA)
|
return inc.(*indicator.SMA)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EWMA is a helper function that returns the exponential weighed moving average indicator of the given interval and the window size.
|
// EWMA is a helper function that returns the exponential weighed moving average indicator of the given interval and the window size.
|
||||||
func (s *StandardIndicatorSet) EWMA(iw types.IntervalWindow) *indicator.EWMA {
|
func (s *StandardIndicatorSet) EWMA(iw types.IntervalWindow) *indicator.EWMA {
|
||||||
inc := s.allocateSimpleIndicator(&indicator.EWMA{IntervalWindow: iw}, iw)
|
inc := s.allocateSimpleIndicator(&indicator.EWMA{IntervalWindow: iw}, iw, "ewma")
|
||||||
return inc.(*indicator.EWMA)
|
return inc.(*indicator.EWMA)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VWMA
|
// VWMA
|
||||||
func (s *StandardIndicatorSet) VWMA(iw types.IntervalWindow) *indicator.VWMA {
|
func (s *StandardIndicatorSet) VWMA(iw types.IntervalWindow) *indicator.VWMA {
|
||||||
inc := s.allocateSimpleIndicator(&indicator.VWMA{IntervalWindow: iw}, iw)
|
inc := s.allocateSimpleIndicator(&indicator.VWMA{IntervalWindow: iw}, iw, "vwma")
|
||||||
return inc.(*indicator.VWMA)
|
return inc.(*indicator.VWMA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StandardIndicatorSet) PivotHigh(iw types.IntervalWindow) *indicator.PivotHigh {
|
||||||
|
inc := s.allocateSimpleIndicator(&indicator.PivotHigh{IntervalWindow: iw}, iw, "pivothigh")
|
||||||
|
return inc.(*indicator.PivotHigh)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StandardIndicatorSet) PivotLow(iw types.IntervalWindow) *indicator.PivotLow {
|
func (s *StandardIndicatorSet) PivotLow(iw types.IntervalWindow) *indicator.PivotLow {
|
||||||
inc := s.allocateSimpleIndicator(&indicator.PivotLow{IntervalWindow: iw}, iw)
|
inc := s.allocateSimpleIndicator(&indicator.PivotLow{IntervalWindow: iw}, iw, "pivotlow")
|
||||||
return inc.(*indicator.PivotLow)
|
return inc.(*indicator.PivotLow)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StandardIndicatorSet) ATR(iw types.IntervalWindow) *indicator.ATR {
|
func (s *StandardIndicatorSet) ATR(iw types.IntervalWindow) *indicator.ATR {
|
||||||
inc := s.allocateSimpleIndicator(&indicator.ATR{IntervalWindow: iw}, iw)
|
inc := s.allocateSimpleIndicator(&indicator.ATR{IntervalWindow: iw}, iw, "atr")
|
||||||
return inc.(*indicator.ATR)
|
return inc.(*indicator.ATR)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StandardIndicatorSet) ATRP(iw types.IntervalWindow) *indicator.ATRP {
|
func (s *StandardIndicatorSet) ATRP(iw types.IntervalWindow) *indicator.ATRP {
|
||||||
inc := s.allocateSimpleIndicator(&indicator.ATRP{IntervalWindow: iw}, iw)
|
inc := s.allocateSimpleIndicator(&indicator.ATRP{IntervalWindow: iw}, iw, "atrp")
|
||||||
return inc.(*indicator.ATRP)
|
return inc.(*indicator.ATRP)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StandardIndicatorSet) EMV(iw types.IntervalWindow) *indicator.EMV {
|
func (s *StandardIndicatorSet) EMV(iw types.IntervalWindow) *indicator.EMV {
|
||||||
inc := s.allocateSimpleIndicator(&indicator.EMV{IntervalWindow: iw}, iw)
|
inc := s.allocateSimpleIndicator(&indicator.EMV{IntervalWindow: iw}, iw, "emv")
|
||||||
return inc.(*indicator.EMV)
|
return inc.(*indicator.EMV)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StandardIndicatorSet) CCI(iw types.IntervalWindow) *indicator.CCI {
|
func (s *StandardIndicatorSet) CCI(iw types.IntervalWindow) *indicator.CCI {
|
||||||
inc := s.allocateSimpleIndicator(&indicator.CCI{IntervalWindow: iw}, iw)
|
inc := s.allocateSimpleIndicator(&indicator.CCI{IntervalWindow: iw}, iw, "cci")
|
||||||
return inc.(*indicator.CCI)
|
return inc.(*indicator.CCI)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StandardIndicatorSet) HULL(iw types.IntervalWindow) *indicator.HULL {
|
func (s *StandardIndicatorSet) HULL(iw types.IntervalWindow) *indicator.HULL {
|
||||||
inc := s.allocateSimpleIndicator(&indicator.HULL{IntervalWindow: iw}, iw)
|
inc := s.allocateSimpleIndicator(&indicator.HULL{IntervalWindow: iw}, iw, "hull")
|
||||||
return inc.(*indicator.HULL)
|
return inc.(*indicator.HULL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StandardIndicatorSet) STOCH(iw types.IntervalWindow) *indicator.STOCH {
|
func (s *StandardIndicatorSet) STOCH(iw types.IntervalWindow) *indicator.STOCH {
|
||||||
inc := s.allocateSimpleIndicator(&indicator.STOCH{IntervalWindow: iw}, iw)
|
inc := s.allocateSimpleIndicator(&indicator.STOCH{IntervalWindow: iw}, iw, "stoch")
|
||||||
return inc.(*indicator.STOCH)
|
return inc.(*indicator.STOCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
65
pkg/indicator/pivothigh.go
Normal file
65
pkg/indicator/pivothigh.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package indicator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/datatype/floats"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate callbackgen -type PivotHigh
|
||||||
|
type PivotHigh struct {
|
||||||
|
types.SeriesBase
|
||||||
|
|
||||||
|
types.IntervalWindow
|
||||||
|
|
||||||
|
Highs floats.Slice
|
||||||
|
Values floats.Slice
|
||||||
|
EndTime time.Time
|
||||||
|
|
||||||
|
updateCallbacks []func(value float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *PivotHigh) Length() int {
|
||||||
|
return inc.Values.Length()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *PivotHigh) Last() float64 {
|
||||||
|
if len(inc.Values) == 0 {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
return inc.Values.Last()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *PivotHigh) Update(value float64) {
|
||||||
|
if len(inc.Highs) == 0 {
|
||||||
|
inc.SeriesBase.Series = inc
|
||||||
|
}
|
||||||
|
|
||||||
|
inc.Highs.Push(value)
|
||||||
|
|
||||||
|
if len(inc.Highs) < inc.Window {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
low, ok := calculatePivotHigh(inc.Highs, inc.Window, inc.RightWindow)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if low > 0.0 {
|
||||||
|
inc.Values.Push(low)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *PivotHigh) PushK(k types.KLine) {
|
||||||
|
if k.EndTime.Before(inc.EndTime) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inc.Update(k.Low.Float64())
|
||||||
|
inc.EndTime = k.EndTime.Time()
|
||||||
|
inc.EmitUpdate(inc.Last())
|
||||||
|
}
|
||||||
|
|
15
pkg/indicator/pivothigh_callbacks.go
Normal file
15
pkg/indicator/pivothigh_callbacks.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Code generated by "callbackgen -type PivotHigh"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package indicator
|
||||||
|
|
||||||
|
import ()
|
||||||
|
|
||||||
|
func (inc *PivotHigh) OnUpdate(cb func(value float64)) {
|
||||||
|
inc.updateCallbacks = append(inc.updateCallbacks, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *PivotHigh) EmitUpdate(value float64) {
|
||||||
|
for _, cb := range inc.updateCallbacks {
|
||||||
|
cb(value)
|
||||||
|
}
|
||||||
|
}
|
249
pkg/strategy/pivotshort/failedbreakhigh.go
Normal file
249
pkg/strategy/pivotshort/failedbreakhigh.go
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
package pivotshort
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/indicator"
|
||||||
|
"github.com/c9s/bbgo/pkg/risk"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FailedBreakHigh -- when price breaks the previous pivot low, we set a trade entry
|
||||||
|
type FailedBreakHigh struct {
|
||||||
|
Symbol string
|
||||||
|
Market types.Market
|
||||||
|
types.IntervalWindow
|
||||||
|
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
// Ratio is a number less than 1.0, price * ratio will be the price triggers the short order.
|
||||||
|
Ratio fixedpoint.Value `json:"ratio"`
|
||||||
|
|
||||||
|
// MarketOrder is the option to enable market order short.
|
||||||
|
MarketOrder bool `json:"marketOrder"`
|
||||||
|
|
||||||
|
Leverage fixedpoint.Value `json:"leverage"`
|
||||||
|
Quantity fixedpoint.Value `json:"quantity"`
|
||||||
|
|
||||||
|
StopEMA *bbgo.StopEMA `json:"stopEMA"`
|
||||||
|
|
||||||
|
TrendEMA *bbgo.TrendEMA `json:"trendEMA"`
|
||||||
|
|
||||||
|
lastFailedBreakHigh, lastHigh fixedpoint.Value
|
||||||
|
|
||||||
|
pivotHigh *indicator.PivotHigh
|
||||||
|
PivotHighPrices []fixedpoint.Value
|
||||||
|
|
||||||
|
orderExecutor *bbgo.GeneralOrderExecutor
|
||||||
|
session *bbgo.ExchangeSession
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FailedBreakHigh) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
||||||
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m})
|
||||||
|
|
||||||
|
if s.StopEMA != nil {
|
||||||
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.StopEMA.Interval})
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.TrendEMA != nil {
|
||||||
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.TrendEMA.Interval})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FailedBreakHigh) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||||
|
s.session = session
|
||||||
|
s.orderExecutor = orderExecutor
|
||||||
|
|
||||||
|
if !s.Enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
position := orderExecutor.Position()
|
||||||
|
symbol := position.Symbol
|
||||||
|
standardIndicator := session.StandardIndicatorSet(s.Symbol)
|
||||||
|
|
||||||
|
s.lastHigh = fixedpoint.Zero
|
||||||
|
s.pivotHigh = standardIndicator.PivotHigh(s.IntervalWindow)
|
||||||
|
|
||||||
|
if s.StopEMA != nil {
|
||||||
|
s.StopEMA.Bind(session, orderExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.TrendEMA != nil {
|
||||||
|
s.TrendEMA.Bind(session, orderExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update pivot low data
|
||||||
|
session.MarketDataStream.OnStart(func() {
|
||||||
|
if s.updatePivotHigh() {
|
||||||
|
bbgo.Notify("%s new pivot high: %f", s.Symbol, s.pivotHigh.Last())
|
||||||
|
}
|
||||||
|
|
||||||
|
s.pilotQuantityCalculation()
|
||||||
|
})
|
||||||
|
|
||||||
|
session.MarketDataStream.OnKLineClosed(types.KLineWith(symbol, s.Interval, func(kline types.KLine) {
|
||||||
|
if s.updatePivotHigh() {
|
||||||
|
// when position is opened, do not send pivot low notify
|
||||||
|
if position.IsOpened(kline.Close) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivotHigh.Last())
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// if the position is already opened, and we just break the low, this checks if the kline closed above the low,
|
||||||
|
// so that we can close the position earlier
|
||||||
|
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(k types.KLine) {
|
||||||
|
if !s.Enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the position is opened, and it's a short position
|
||||||
|
if !position.IsOpened(k.Close) || !position.IsShort() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we recorded the last break low
|
||||||
|
if s.lastFailedBreakHigh.IsZero() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// the kline opened below the last break low, and closed above the last break low
|
||||||
|
if k.Open.Compare(s.lastFailedBreakHigh) < 0 && k.Close.Compare(s.lastFailedBreakHigh) > 0 {
|
||||||
|
bbgo.Notify("kLine closed above the last break low, triggering stop earlier")
|
||||||
|
if err := s.orderExecutor.ClosePosition(context.Background(), one, "fakeBreakStop"); err != nil {
|
||||||
|
log.WithError(err).Error("position close error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset to zero
|
||||||
|
s.lastFailedBreakHigh = fixedpoint.Zero
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, types.Interval1m, func(kline types.KLine) {
|
||||||
|
if len(s.PivotHighPrices) == 0 || s.lastHigh.IsZero() {
|
||||||
|
log.Infof("currently there is no pivot high prices, can not check failed break high...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
previousHigh := s.lastHigh
|
||||||
|
ratio := fixedpoint.One.Add(s.Ratio)
|
||||||
|
breakPrice := previousHigh.Mul(ratio)
|
||||||
|
|
||||||
|
openPrice := kline.Open
|
||||||
|
closePrice := kline.Close
|
||||||
|
|
||||||
|
// we need few conditions:
|
||||||
|
// 1) kline.High is higher than the previous high
|
||||||
|
// 2) kline.Close is lower than the previous high
|
||||||
|
// 3) kline.Close is lower than kline.Open
|
||||||
|
if kline.High.Compare(breakPrice) < 0 || closePrice.Compare(breakPrice) >= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if closePrice.Compare(openPrice) > 0 {
|
||||||
|
bbgo.Notify("the closed price is higher than the open price, skip failed break high short")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bbgo.Notify("%s FailedBreakHigh signal detected, closed price %f < breakPrice %f", kline.Symbol, closePrice.Float64(), breakPrice.Float64())
|
||||||
|
|
||||||
|
if s.lastFailedBreakHigh.IsZero() || previousHigh.Compare(s.lastFailedBreakHigh) < 0 {
|
||||||
|
s.lastFailedBreakHigh = previousHigh
|
||||||
|
}
|
||||||
|
|
||||||
|
if position.IsOpened(kline.Close) {
|
||||||
|
bbgo.Notify("position is already opened, skip")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// trend EMA protection
|
||||||
|
if s.TrendEMA != nil && !s.TrendEMA.GradientAllowed() {
|
||||||
|
bbgo.Notify("trendEMA protection: close price %f, gradient %f", kline.Close.Float64(), s.TrendEMA.Gradient())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop EMA protection
|
||||||
|
if s.StopEMA != nil {
|
||||||
|
if !s.StopEMA.Allowed(closePrice) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// graceful cancel all active orders
|
||||||
|
_ = orderExecutor.GracefulCancel(ctx)
|
||||||
|
|
||||||
|
quantity, err := risk.CalculateBaseQuantity(s.session, s.Market, closePrice, s.Quantity, s.Leverage)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("quantity calculation error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if quantity.IsZero() {
|
||||||
|
log.Warn("quantity is zero, can not submit order, skip")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.MarketOrder {
|
||||||
|
bbgo.Notify("%s price %f failed breaking the previous high %f with ratio %f, submitting market sell to open a short position", symbol, kline.Close.Float64(), previousHigh.Float64(), s.Ratio.Float64())
|
||||||
|
_, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||||
|
Symbol: s.Symbol,
|
||||||
|
Side: types.SideTypeSell,
|
||||||
|
Type: types.OrderTypeMarket,
|
||||||
|
Quantity: quantity,
|
||||||
|
MarginSideEffect: types.SideEffectTypeMarginBuy,
|
||||||
|
Tag: "FailedBreakHighMarket",
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sellPrice := previousHigh
|
||||||
|
|
||||||
|
bbgo.Notify("%s price %f failed breaking the previous high %f with ratio %f, submitting limit sell @ %f", symbol, kline.Close.Float64(), previousHigh.Float64(), s.Ratio.Float64(), sellPrice.Float64())
|
||||||
|
_, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||||
|
Symbol: kline.Symbol,
|
||||||
|
Side: types.SideTypeSell,
|
||||||
|
Type: types.OrderTypeLimit,
|
||||||
|
Price: sellPrice,
|
||||||
|
Quantity: quantity,
|
||||||
|
MarginSideEffect: types.SideEffectTypeMarginBuy,
|
||||||
|
Tag: "FailedBreakHighLimit",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FailedBreakHigh) pilotQuantityCalculation() {
|
||||||
|
log.Infof("pilot calculation for max position: last low = %f, quantity = %f, leverage = %f",
|
||||||
|
s.lastHigh.Float64(),
|
||||||
|
s.Quantity.Float64(),
|
||||||
|
s.Leverage.Float64())
|
||||||
|
|
||||||
|
quantity, err := risk.CalculateBaseQuantity(s.session, s.Market, s.lastHigh, 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 failed break high short", s.Symbol, quantity.Float64())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FailedBreakHigh) updatePivotHigh() bool {
|
||||||
|
lastHigh := fixedpoint.NewFromFloat(s.pivotHigh.Last())
|
||||||
|
if lastHigh.IsZero() || lastHigh.Compare(s.lastHigh) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
s.lastHigh = lastHigh
|
||||||
|
s.PivotHighPrices = append(s.PivotHighPrices, lastHigh)
|
||||||
|
return true
|
||||||
|
}
|
|
@ -42,13 +42,12 @@ type Strategy struct {
|
||||||
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
||||||
|
|
||||||
// BreakLow is one of the entry method
|
// BreakLow is one of the entry method
|
||||||
BreakLow *BreakLow `json:"breakLow"`
|
BreakLow *BreakLow `json:"breakLow"`
|
||||||
|
FailedBreakHigh *FailedBreakHigh `json:"failedBreakHigh"`
|
||||||
|
|
||||||
// ResistanceShort is one of the entry method
|
// ResistanceShort is one of the entry method
|
||||||
ResistanceShort *ResistanceShort `json:"resistanceShort"`
|
ResistanceShort *ResistanceShort `json:"resistanceShort"`
|
||||||
|
|
||||||
SupportTakeProfit []*bbgo.SupportTakeProfit `json:"supportTakeProfit"`
|
|
||||||
|
|
||||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||||
|
|
||||||
session *bbgo.ExchangeSession
|
session *bbgo.ExchangeSession
|
||||||
|
@ -80,10 +79,9 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
s.BreakLow.Subscribe(session)
|
s.BreakLow.Subscribe(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range s.SupportTakeProfit {
|
if s.FailedBreakHigh != nil {
|
||||||
m := s.SupportTakeProfit[i]
|
dynamic.InheritStructValues(s.FailedBreakHigh, s)
|
||||||
dynamic.InheritStructValues(m, s)
|
s.FailedBreakHigh.Subscribe(session)
|
||||||
m.Subscribe(session)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bbgo.IsBackTesting {
|
if !bbgo.IsBackTesting {
|
||||||
|
@ -157,8 +155,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 i := range s.SupportTakeProfit {
|
if s.FailedBreakHigh != nil {
|
||||||
s.SupportTakeProfit[i].Bind(session, s.orderExecutor)
|
s.FailedBreakHigh.Bind(session, s.orderExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user