bbgo_origin/pkg/strategy/pivotshort/resistance.go
c9s 9374125712
pivotshort: pull out break low logics
Signed-off-by: c9s <yoanlin93@gmail.com>
2022-07-01 17:22:09 +08:00

173 lines
5.1 KiB
Go

package pivotshort
import (
"context"
"sort"
"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",
MarginSideEffect: types.SideEffectTypeMarginBuy,
})
// 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...)
}
func findPossibleResistancePrices(closePrice float64, minDistance float64, lows []float64) []float64 {
// sort float64 in increasing order
// lower to higher prices
sort.Float64s(lows)
var resistancePrices []float64
for _, low := range lows {
if low < closePrice {
continue
}
last := closePrice
if len(resistancePrices) > 0 {
last = resistancePrices[len(resistancePrices)-1]
}
if (low / last) < (1.0 + minDistance) {
continue
}
resistancePrices = append(resistancePrices, low)
}
return resistancePrices
}