mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 23:05:15 +00:00
pivotshort: move resistance short to a single file
This commit is contained in:
parent
454036b166
commit
503d851c9d
145
pkg/strategy/pivotshort/resistance.go
Normal file
145
pkg/strategy/pivotshort/resistance.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
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/types"
|
||||
)
|
||||
|
||||
type ResistanceShort struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Symbol string `json:"-"`
|
||||
Market types.Market `json:"-"`
|
||||
|
||||
types.IntervalWindow
|
||||
|
||||
MinDistance fixedpoint.Value `json:"minDistance"`
|
||||
NumLayers int `json:"numLayers"`
|
||||
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
|
||||
|
||||
activeOrders *bbgo.ActiveOrderBook
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||
s.session = session
|
||||
s.orderExecutor = orderExecutor
|
||||
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
||||
s.activeOrders.BindStream(session.UserDataStream)
|
||||
|
||||
store, _ := session.MarketDataStore(s.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
|
||||
if lastKLine != nil {
|
||||
s.findNextResistancePriceAndPlaceOrders(lastKLine.Close)
|
||||
}
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
|
||||
position := s.orderExecutor.Position()
|
||||
if position.IsOpened(kline.Close) {
|
||||
return
|
||||
}
|
||||
|
||||
s.findNextResistancePriceAndPlaceOrders(kline.Close)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) findNextResistancePriceAndPlaceOrders(closePrice fixedpoint.Value) {
|
||||
|
||||
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 {
|
||||
bbgo.Notify("Found next resistance price: %f", nextResistancePrice.Float64())
|
||||
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 s.activeOrders.NumOfOrders() > 0 {
|
||||
if err := s.orderExecutor.GracefulCancelActiveOrderBook(ctx, s.activeOrders); err != nil {
|
||||
log.WithError(err).Errorf("can not cancel resistance orders: %+v", s.activeOrders.Orders())
|
||||
}
|
||||
}
|
||||
|
||||
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.activeOrders.Add(createdOrders...)
|
||||
}
|
|
@ -48,139 +48,6 @@ type BreakLow struct {
|
|||
StopEMA *types.IntervalWindow `json:"stopEMA"`
|
||||
}
|
||||
|
||||
type ResistanceShort struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Symbol string `json:"-"`
|
||||
Market types.Market `json:"-"`
|
||||
|
||||
types.IntervalWindow
|
||||
|
||||
MinDistance fixedpoint.Value `json:"minDistance"`
|
||||
NumLayers int `json:"numLayers"`
|
||||
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
|
||||
|
||||
activeOrders *bbgo.ActiveOrderBook
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||
s.session = session
|
||||
s.orderExecutor = orderExecutor
|
||||
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
||||
s.activeOrders.BindStream(session.UserDataStream)
|
||||
|
||||
store, _ := session.MarketDataStore(s.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
|
||||
if lastKLine != nil {
|
||||
s.findNextResistancePriceAndPlaceOrders(lastKLine.Close)
|
||||
}
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
|
||||
position := s.orderExecutor.Position()
|
||||
if position.IsOpened(kline.Close) {
|
||||
return
|
||||
}
|
||||
|
||||
s.findNextResistancePriceAndPlaceOrders(kline.Close)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) findNextResistancePriceAndPlaceOrders(closePrice fixedpoint.Value) {
|
||||
|
||||
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 {
|
||||
bbgo.Notify("Found next resistance price: %f", nextResistancePrice.Float64())
|
||||
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.activeOrders.Orders()...); err != nil {
|
||||
log.WithError(err).Errorf("can not cancel resistance orders: %+v", s.activeOrders.Orders())
|
||||
}
|
||||
|
||||
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.activeOrders.Add(createdOrders...)
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
Environment *bbgo.Environment
|
||||
Symbol string `json:"symbol"`
|
||||
|
@ -356,8 +223,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
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
|
||||
if !(openPrice.Compare(breakPrice) > 0 && closePrice.Compare(breakPrice) < 0) {
|
||||
return
|
||||
}
|
||||
|
@ -378,6 +244,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
}
|
||||
|
||||
// graceful cancel all active orders
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
|
||||
quantity := s.useQuantityOrBaseBalance(s.BreakLow.Quantity)
|
||||
|
|
Loading…
Reference in New Issue
Block a user