mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 01:01:56 +00:00
pivotshort: refactor ResistanceShort entry method
This commit is contained in:
parent
bfcbf8566e
commit
3e6b975c2c
|
@ -46,12 +46,14 @@ exchangeStrategies:
|
|||
window: 99
|
||||
|
||||
resistanceShort:
|
||||
enabled: false
|
||||
enabled: true
|
||||
interval: 1h
|
||||
window: 10
|
||||
window: 8
|
||||
|
||||
quantity: 10.0
|
||||
|
||||
# minDistance is used to ignore the place that is too near to the current price
|
||||
minDistance: 3%
|
||||
# stopLossPercentage: 1%
|
||||
|
||||
# ratio is the ratio of the resistance price,
|
||||
# higher the ratio, lower the price
|
||||
|
|
|
@ -44,7 +44,7 @@ func (m *ExitMethod) Inherit(parent interface{}) {
|
|||
continue
|
||||
}
|
||||
|
||||
dynamic.MergeStructValues(fieldValue.Interface(), parent)
|
||||
dynamic.InheritStructValues(fieldValue.Interface(), parent)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ package dynamic
|
|||
|
||||
import "reflect"
|
||||
|
||||
// MergeStructValues merges the field value from the source struct to the dest struct.
|
||||
// InheritStructValues merges the field value from the source struct to the dest struct.
|
||||
// Only fields with the same type and the same name will be updated.
|
||||
func MergeStructValues(dst, src interface{}) {
|
||||
func InheritStructValues(dst, src interface{}) {
|
||||
if dst == nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -21,14 +21,14 @@ func Test_reflectMergeStructFields(t *testing.T) {
|
|||
t.Run("zero value", func(t *testing.T) {
|
||||
a := &TestStrategy{Symbol: "BTCUSDT"}
|
||||
b := &struct{ Symbol string }{Symbol: ""}
|
||||
MergeStructValues(b, a)
|
||||
InheritStructValues(b, a)
|
||||
assert.Equal(t, "BTCUSDT", b.Symbol)
|
||||
})
|
||||
|
||||
t.Run("non-zero value", func(t *testing.T) {
|
||||
a := &TestStrategy{Symbol: "BTCUSDT"}
|
||||
b := &struct{ Symbol string }{Symbol: "ETHUSDT"}
|
||||
MergeStructValues(b, a)
|
||||
InheritStructValues(b, a)
|
||||
assert.Equal(t, "ETHUSDT", b.Symbol, "should be the original value")
|
||||
})
|
||||
|
||||
|
@ -45,7 +45,7 @@ func Test_reflectMergeStructFields(t *testing.T) {
|
|||
Symbol string
|
||||
types.IntervalWindow
|
||||
}{}
|
||||
MergeStructValues(b, a)
|
||||
InheritStructValues(b, a)
|
||||
assert.Equal(t, iw, b.IntervalWindow)
|
||||
assert.Equal(t, "BTCUSDT", b.Symbol)
|
||||
})
|
||||
|
@ -62,7 +62,7 @@ func Test_reflectMergeStructFields(t *testing.T) {
|
|||
}{
|
||||
IntervalWindow: types.IntervalWindow{Interval: types.Interval5m, Window: 9},
|
||||
}
|
||||
MergeStructValues(b, a)
|
||||
InheritStructValues(b, a)
|
||||
assert.Equal(t, types.IntervalWindow{Interval: types.Interval5m, Window: 9}, b.IntervalWindow)
|
||||
})
|
||||
|
||||
|
@ -75,7 +75,7 @@ func Test_reflectMergeStructFields(t *testing.T) {
|
|||
b := &struct {
|
||||
A string
|
||||
}{}
|
||||
MergeStructValues(b, a)
|
||||
InheritStructValues(b, a)
|
||||
assert.Equal(t, "", b.A)
|
||||
assert.Equal(t, 1.99, a.A)
|
||||
})
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/dynamic"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/indicator"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
|
@ -49,6 +50,8 @@ type BreakLow struct {
|
|||
|
||||
type ResistanceShort struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Symbol string `json:"-"`
|
||||
Market types.Market `json:"-"`
|
||||
|
||||
types.IntervalWindow
|
||||
|
||||
|
@ -57,15 +60,126 @@ type ResistanceShort struct {
|
|||
LayerSpread fixedpoint.Value `json:"layerSpread"`
|
||||
Quantity fixedpoint.Value `json:"quantity"`
|
||||
Ratio fixedpoint.Value `json:"ratio"`
|
||||
|
||||
session *bbgo.ExchangeSession
|
||||
orderExecutor *bbgo.GeneralOrderExecutor
|
||||
|
||||
resistancePivot *indicator.Pivot
|
||||
resistancePrices []float64
|
||||
nextResistancePrice fixedpoint.Value
|
||||
|
||||
resistanceOrders []types.Order
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
CatBounceRatio fixedpoint.Value `json:"catBounceRatio"`
|
||||
NumLayers int `json:"numLayers"`
|
||||
TotalQuantity fixedpoint.Value `json:"totalQuantity"`
|
||||
func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||
s.session = session
|
||||
s.orderExecutor = orderExecutor
|
||||
|
||||
Quantity fixedpoint.Value `json:"quantity"`
|
||||
MarginSideEffect types.MarginOrderSideEffectType `json:"marginOrderSideEffect"`
|
||||
position := orderExecutor.Position()
|
||||
symbol := position.Symbol
|
||||
store, _ := session.MarketDataStore(symbol)
|
||||
|
||||
s.resistancePivot = &indicator.Pivot{IntervalWindow: s.IntervalWindow}
|
||||
s.resistancePivot.Bind(store)
|
||||
|
||||
// preload history kline data to the resistance pivot indicator
|
||||
// we use the last kline to find the higher lows
|
||||
lastKLine := preloadPivot(s.resistancePivot, store)
|
||||
|
||||
// use the last kline from the history before we get the next closed kline
|
||||
s.findNextResistancePriceAndPlaceOrders(lastKLine.Close)
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
if kline.Symbol != s.Symbol || kline.Interval != s.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
s.findNextResistancePriceAndPlaceOrders(kline.Close)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) findNextResistancePriceAndPlaceOrders(closePrice fixedpoint.Value) {
|
||||
position := s.orderExecutor.Position()
|
||||
if position.IsOpened(closePrice) {
|
||||
return
|
||||
}
|
||||
|
||||
minDistance := s.MinDistance.Float64()
|
||||
lows := s.resistancePivot.Lows
|
||||
resistancePrices := findPossibleResistancePrices(closePrice.Float64(), minDistance, lows)
|
||||
|
||||
log.Infof("last price: %f, possible resistance prices: %+v", closePrice.Float64(), resistancePrices)
|
||||
|
||||
ctx := context.Background()
|
||||
if len(resistancePrices) > 0 {
|
||||
nextResistancePrice := fixedpoint.NewFromFloat(resistancePrices[0])
|
||||
if nextResistancePrice.Compare(s.nextResistancePrice) != 0 {
|
||||
s.nextResistancePrice = nextResistancePrice
|
||||
s.placeResistanceOrders(ctx, nextResistancePrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) placeResistanceOrders(ctx context.Context, resistancePrice fixedpoint.Value) {
|
||||
futuresMode := s.session.Futures || s.session.IsolatedFutures
|
||||
_ = futuresMode
|
||||
|
||||
totalQuantity := s.Quantity
|
||||
numLayers := s.NumLayers
|
||||
if numLayers == 0 {
|
||||
numLayers = 1
|
||||
}
|
||||
|
||||
numLayersF := fixedpoint.NewFromInt(int64(numLayers))
|
||||
layerSpread := s.LayerSpread
|
||||
quantity := totalQuantity.Div(numLayersF)
|
||||
|
||||
if err := s.orderExecutor.CancelOrders(ctx, s.resistanceOrders...); err != nil {
|
||||
log.WithError(err).Errorf("can not cancel resistance orders: %+v", s.resistanceOrders)
|
||||
}
|
||||
s.resistanceOrders = nil
|
||||
|
||||
log.Infof("placing resistance orders: resistance price = %f, layer quantity = %f, num of layers = %d", resistancePrice.Float64(), quantity.Float64(), numLayers)
|
||||
|
||||
var orderForms []types.SubmitOrder
|
||||
for i := 0; i < numLayers; i++ {
|
||||
balances := s.session.GetAccount().Balances()
|
||||
quoteBalance := balances[s.Market.QuoteCurrency]
|
||||
baseBalance := balances[s.Market.BaseCurrency]
|
||||
_ = quoteBalance
|
||||
_ = baseBalance
|
||||
|
||||
// price = (resistance_price * (1.0 + ratio)) * ((1.0 + layerSpread) * i)
|
||||
price := resistancePrice.Mul(fixedpoint.One.Add(s.Ratio))
|
||||
spread := layerSpread.Mul(fixedpoint.NewFromInt(int64(i)))
|
||||
price = price.Add(spread)
|
||||
log.Infof("price = %f", price.Float64())
|
||||
|
||||
log.Infof("placing bounce short order #%d: price = %f, quantity = %f", i, price.Float64(), quantity.Float64())
|
||||
|
||||
orderForms = append(orderForms, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeLimitMaker,
|
||||
Price: price,
|
||||
Quantity: quantity,
|
||||
Tag: "resistanceShort",
|
||||
})
|
||||
|
||||
// TODO: fix futures mode later
|
||||
/*
|
||||
if futuresMode {
|
||||
if quantity.Mul(price).Compare(quoteBalance.Available) <= 0 {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
createdOrders, err := s.orderExecutor.SubmitOrders(ctx, orderForms...)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("can not place resistance order")
|
||||
}
|
||||
s.resistanceOrders = createdOrders
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
|
@ -81,11 +195,12 @@ type Strategy struct {
|
|||
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
|
||||
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
||||
|
||||
// BreakLow is one of the entry method
|
||||
BreakLow BreakLow `json:"breakLow"`
|
||||
|
||||
// ResistanceShort is one of the entry method
|
||||
ResistanceShort *ResistanceShort `json:"resistanceShort"`
|
||||
|
||||
Entry Entry `json:"entry"`
|
||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||
|
||||
session *bbgo.ExchangeSession
|
||||
|
@ -96,7 +211,6 @@ type Strategy struct {
|
|||
resistancePivot *indicator.Pivot
|
||||
stopEWMA *indicator.EWMA
|
||||
pivotLowPrices []fixedpoint.Value
|
||||
resistancePrices []float64
|
||||
currentBounceShortPrice fixedpoint.Value
|
||||
|
||||
// StrategyController
|
||||
|
@ -112,6 +226,7 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
|||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m})
|
||||
|
||||
if s.ResistanceShort != nil && s.ResistanceShort.Enabled {
|
||||
dynamic.InheritStructValues(s.ResistanceShort, s)
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.ResistanceShort.Interval})
|
||||
}
|
||||
|
||||
|
@ -182,8 +297,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
|
||||
s.pivot = &indicator.Pivot{IntervalWindow: s.IntervalWindow}
|
||||
s.pivot.Bind(store)
|
||||
|
||||
lastKLine := preloadPivot(s.pivot, store)
|
||||
preloadPivot(s.pivot, store)
|
||||
|
||||
// update pivot low data
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
|
@ -204,11 +318,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.pivotLowPrices = append(s.pivotLowPrices, s.lastLow)
|
||||
})
|
||||
|
||||
if s.ResistanceShort != nil && s.ResistanceShort.Enabled {
|
||||
s.resistancePivot = &indicator.Pivot{IntervalWindow: s.ResistanceShort.IntervalWindow}
|
||||
s.resistancePivot.Bind(store)
|
||||
}
|
||||
|
||||
if s.BreakLow.StopEMA != nil {
|
||||
s.stopEWMA = standardIndicator.EWMA(*s.BreakLow.StopEMA)
|
||||
}
|
||||
|
@ -218,35 +327,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
|
||||
if s.ResistanceShort != nil && s.ResistanceShort.Enabled {
|
||||
if s.resistancePivot != nil {
|
||||
preloadPivot(s.resistancePivot, store)
|
||||
}
|
||||
|
||||
session.UserDataStream.OnStart(func() {
|
||||
if lastKLine == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.resistancePivot != nil {
|
||||
lows := s.resistancePivot.Lows
|
||||
minDistance := s.ResistanceShort.MinDistance.Float64()
|
||||
closePrice := lastKLine.Close.Float64()
|
||||
s.resistancePrices = findPossibleResistancePrices(closePrice, minDistance, lows)
|
||||
log.Infof("last price: %f, possible resistance prices: %+v", closePrice, s.resistancePrices)
|
||||
|
||||
if len(s.resistancePrices) > 0 {
|
||||
resistancePrice := fixedpoint.NewFromFloat(s.resistancePrices[0])
|
||||
if resistancePrice.Compare(s.currentBounceShortPrice) != 0 {
|
||||
log.Infof("updating resistance price... possible resistance prices: %+v", s.resistancePrices)
|
||||
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
|
||||
s.currentBounceShortPrice = resistancePrice
|
||||
s.placeBounceSellOrders(ctx, s.currentBounceShortPrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
s.ResistanceShort.Bind(session, s.orderExecutor)
|
||||
}
|
||||
|
||||
// Always check whether you can open a short position or not
|
||||
|
@ -321,40 +402,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
})
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
// StrategyController
|
||||
if s.Status != types.StrategyStatusRunning {
|
||||
return
|
||||
}
|
||||
|
||||
if s.ResistanceShort == nil || !s.ResistanceShort.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
if kline.Symbol != s.Symbol || kline.Interval != s.ResistanceShort.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
if s.resistancePivot != nil {
|
||||
closePrice := kline.Close.Float64()
|
||||
minDistance := s.ResistanceShort.MinDistance.Float64()
|
||||
lows := s.resistancePivot.Lows
|
||||
s.resistancePrices = findPossibleResistancePrices(closePrice, minDistance, lows)
|
||||
|
||||
if len(s.resistancePrices) > 0 {
|
||||
resistancePrice := fixedpoint.NewFromFloat(s.resistancePrices[0])
|
||||
if resistancePrice.Compare(s.currentBounceShortPrice) != 0 {
|
||||
log.Infof("updating resistance price... possible resistance prices: %+v", s.resistancePrices)
|
||||
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
|
||||
s.currentBounceShortPrice = resistancePrice
|
||||
s.placeBounceSellOrders(ctx, s.currentBounceShortPrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if !bbgo.IsBackTesting {
|
||||
// use market trade to submit short order
|
||||
session.MarketDataStream.OnMarketTrade(func(trade types.Trade) {
|
||||
|
@ -380,46 +427,6 @@ func (s *Strategy) findHigherPivotLow(price fixedpoint.Value) (fixedpoint.Value,
|
|||
return price, false
|
||||
}
|
||||
|
||||
func (s *Strategy) placeBounceSellOrders(ctx context.Context, resistancePrice fixedpoint.Value) {
|
||||
futuresMode := s.session.Futures || s.session.IsolatedFutures
|
||||
totalQuantity := s.ResistanceShort.Quantity
|
||||
numLayers := s.ResistanceShort.NumLayers
|
||||
if numLayers == 0 {
|
||||
numLayers = 1
|
||||
}
|
||||
|
||||
numLayersF := fixedpoint.NewFromInt(int64(numLayers))
|
||||
|
||||
layerSpread := s.ResistanceShort.LayerSpread
|
||||
quantity := totalQuantity.Div(numLayersF)
|
||||
|
||||
log.Infof("placing bounce short orders: resistance price = %f, layer quantity = %f, num of layers = %d", resistancePrice.Float64(), quantity.Float64(), numLayers)
|
||||
|
||||
for i := 0; i < numLayers; i++ {
|
||||
balances := s.session.GetAccount().Balances()
|
||||
quoteBalance := balances[s.Market.QuoteCurrency]
|
||||
baseBalance := balances[s.Market.BaseCurrency]
|
||||
|
||||
// price = (resistance_price * (1.0 + ratio)) * ((1.0 + layerSpread) * i)
|
||||
price := resistancePrice.Mul(fixedpoint.One.Add(s.ResistanceShort.Ratio))
|
||||
spread := layerSpread.Mul(fixedpoint.NewFromInt(int64(i)))
|
||||
price = price.Add(spread)
|
||||
log.Infof("price = %f", price.Float64())
|
||||
|
||||
log.Infof("placing bounce short order #%d: price = %f, quantity = %f", i, price.Float64(), quantity.Float64())
|
||||
|
||||
if futuresMode {
|
||||
if quantity.Mul(price).Compare(quoteBalance.Available) <= 0 {
|
||||
s.placeOrder(ctx, price, quantity)
|
||||
}
|
||||
} else {
|
||||
if quantity.Compare(baseBalance.Available) <= 0 {
|
||||
s.placeOrder(ctx, price, quantity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) placeOrder(ctx context.Context, price fixedpoint.Value, quantity fixedpoint.Value) {
|
||||
_, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
|
@ -474,6 +481,7 @@ func (s *Strategy) placeMarketSell(ctx context.Context, quantity fixedpoint.Valu
|
|||
|
||||
func findPossibleResistancePrices(closePrice float64, minDistance float64, lows []float64) []float64 {
|
||||
// sort float64 in increasing order
|
||||
// lower to higher prices
|
||||
sort.Float64s(lows)
|
||||
|
||||
var resistancePrices []float64
|
||||
|
|
|
@ -273,6 +273,10 @@ func (p *Position) IsClosed() bool {
|
|||
return p.Base.Sign() == 0
|
||||
}
|
||||
|
||||
func (p *Position) IsOpened(currentPrice fixedpoint.Value) bool {
|
||||
return p.IsClosed() || !p.IsDust(currentPrice)
|
||||
}
|
||||
|
||||
func (p *Position) Type() PositionType {
|
||||
if p.Base.Sign() > 0 {
|
||||
return PositionLong
|
||||
|
|
Loading…
Reference in New Issue
Block a user