mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
reimplement placeBounceSellOrders
This commit is contained in:
parent
46450c0122
commit
ec68dc2f40
|
@ -45,11 +45,19 @@ exchangeStrategies:
|
||||||
window: 99
|
window: 99
|
||||||
|
|
||||||
bounceShort:
|
bounceShort:
|
||||||
|
interval: 1h
|
||||||
|
window: 10
|
||||||
quantity: 10.0
|
quantity: 10.0
|
||||||
|
minDistance: 3%
|
||||||
# stopLossPercentage: 1%
|
# stopLossPercentage: 1%
|
||||||
|
|
||||||
|
# ratio is the ratio of the resistance price,
|
||||||
|
# higher the ratio, lower the price
|
||||||
|
# first_layer_price = resistance_price * (1 - ratio)
|
||||||
|
# second_layer_price = (resistance_price * (1 - ratio)) * (1.0 + layerSpread)
|
||||||
|
ratio: 0.1%
|
||||||
numOfLayers: 10
|
numOfLayers: 10
|
||||||
layerSpread: 0.1%
|
layerSpread: 0.1%
|
||||||
pivotRatio: 0.1%
|
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
# roiStopLossPercentage is the stop loss percentage of the position ROI (currently the price change)
|
# roiStopLossPercentage is the stop loss percentage of the position ROI (currently the price change)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -74,6 +75,16 @@ type BreakLow struct {
|
||||||
StopEMA *types.IntervalWindow `json:"stopEMA"`
|
StopEMA *types.IntervalWindow `json:"stopEMA"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BounceShort struct {
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
CatBounceRatio fixedpoint.Value `json:"catBounceRatio"`
|
CatBounceRatio fixedpoint.Value `json:"catBounceRatio"`
|
||||||
NumLayers int `json:"numLayers"`
|
NumLayers int `json:"numLayers"`
|
||||||
|
@ -119,6 +130,9 @@ type Strategy struct {
|
||||||
TradeStats *TradeStats `persistence:"trade_stats"`
|
TradeStats *TradeStats `persistence:"trade_stats"`
|
||||||
|
|
||||||
BreakLow BreakLow `json:"breakLow"`
|
BreakLow BreakLow `json:"breakLow"`
|
||||||
|
|
||||||
|
BounceShort *BounceShort `json:"bounceShort"`
|
||||||
|
|
||||||
Entry Entry `json:"entry"`
|
Entry Entry `json:"entry"`
|
||||||
Exit Exit `json:"exit"`
|
Exit Exit `json:"exit"`
|
||||||
|
|
||||||
|
@ -130,8 +144,11 @@ type Strategy struct {
|
||||||
|
|
||||||
lastLow fixedpoint.Value
|
lastLow fixedpoint.Value
|
||||||
pivot *indicator.Pivot
|
pivot *indicator.Pivot
|
||||||
|
resistancePivot *indicator.Pivot
|
||||||
stopEWMA *indicator.EWMA
|
stopEWMA *indicator.EWMA
|
||||||
pivotLowPrices []fixedpoint.Value
|
pivotLowPrices []fixedpoint.Value
|
||||||
|
resistancePrices []float64
|
||||||
|
currentBounceShortPrice fixedpoint.Value
|
||||||
|
|
||||||
// StrategyController
|
// StrategyController
|
||||||
bbgo.StrategyController
|
bbgo.StrategyController
|
||||||
|
@ -145,6 +162,10 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
log.Infof("subscribe %s", s.Symbol)
|
log.Infof("subscribe %s", s.Symbol)
|
||||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
||||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m})
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m})
|
||||||
|
|
||||||
|
if s.BounceShort != nil {
|
||||||
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.BounceShort.Interval})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) submitOrders(ctx context.Context, orderExecutor bbgo.OrderExecutor, submitOrders ...types.SubmitOrder) {
|
func (s *Strategy) submitOrders(ctx context.Context, orderExecutor bbgo.OrderExecutor, submitOrders ...types.SubmitOrder) {
|
||||||
|
@ -280,9 +301,15 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.tradeCollector.BindStream(session.UserDataStream)
|
s.tradeCollector.BindStream(session.UserDataStream)
|
||||||
|
|
||||||
store, _ := session.MarketDataStore(s.Symbol)
|
store, _ := session.MarketDataStore(s.Symbol)
|
||||||
|
|
||||||
s.pivot = &indicator.Pivot{IntervalWindow: s.IntervalWindow}
|
s.pivot = &indicator.Pivot{IntervalWindow: s.IntervalWindow}
|
||||||
s.pivot.Bind(store)
|
s.pivot.Bind(store)
|
||||||
|
|
||||||
|
if s.BounceShort != nil {
|
||||||
|
s.resistancePivot = &indicator.Pivot{IntervalWindow: s.BounceShort.IntervalWindow}
|
||||||
|
s.resistancePivot.Bind(store)
|
||||||
|
}
|
||||||
|
|
||||||
standardIndicator, _ := session.StandardIndicatorSet(s.Symbol)
|
standardIndicator, _ := session.StandardIndicatorSet(s.Symbol)
|
||||||
if s.BreakLow.StopEMA != nil {
|
if s.BreakLow.StopEMA != nil {
|
||||||
s.stopEWMA = standardIndicator.EWMA(*s.BreakLow.StopEMA)
|
s.stopEWMA = standardIndicator.EWMA(*s.BreakLow.StopEMA)
|
||||||
|
@ -291,18 +318,26 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.lastLow = fixedpoint.Zero
|
s.lastLow = fixedpoint.Zero
|
||||||
|
|
||||||
session.UserDataStream.OnStart(func() {
|
session.UserDataStream.OnStart(func() {
|
||||||
if klines, ok := store.KLinesOfInterval(s.Interval); ok {
|
lastKLine := s.preloadPivot(s.pivot, store)
|
||||||
last := (*klines)[len(*klines)-1]
|
|
||||||
log.Debugf("updating pivot indicator: %d klines", len(*klines))
|
if s.resistancePivot != nil {
|
||||||
for i := s.pivot.Window; i < len(*klines); i++ {
|
s.preloadPivot(s.resistancePivot, store)
|
||||||
s.pivot.Update((*klines)[0 : i+1])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("current %s price: %f", s.Symbol, last.Close.Float64())
|
if lastKLine == nil {
|
||||||
log.Infof("found %s previous lows: %v", s.Symbol, s.pivot.Lows)
|
return
|
||||||
log.Infof("found %s previous highs: %v", s.Symbol, s.pivot.Highs)
|
}
|
||||||
|
|
||||||
|
lows := s.resistancePivot.Lows
|
||||||
|
minDistance := s.BounceShort.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 {
|
||||||
|
s.currentBounceShortPrice = fixedpoint.NewFromFloat(s.resistancePrices[0])
|
||||||
|
s.placeBounceSellOrders(ctx, s.currentBounceShortPrice, orderExecutor)
|
||||||
}
|
}
|
||||||
// s.placeBounceSellOrders(ctx, limitPrice, price, orderExecutor)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Always check whether you can open a short position or not
|
// Always check whether you can open a short position or not
|
||||||
|
@ -410,6 +445,21 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||||
|
if s.BounceShort == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if kline.Symbol != s.Symbol || kline.Interval != s.BounceShort.Interval {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
closePrice := kline.Close.Float64()
|
||||||
|
minDistance := s.BounceShort.MinDistance.Float64()
|
||||||
|
lows := s.resistancePivot.Lows
|
||||||
|
s.resistancePrices = findPossibleResistancePrices(closePrice, minDistance, lows)
|
||||||
|
})
|
||||||
|
|
||||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||||
if kline.Symbol != s.Symbol || kline.Interval != s.Interval {
|
if kline.Symbol != s.Symbol || kline.Interval != s.Interval {
|
||||||
return
|
return
|
||||||
|
@ -451,50 +501,95 @@ func (s *Strategy) findHigherPivotLow(price fixedpoint.Value) (fixedpoint.Value,
|
||||||
return price, false
|
return price, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) placeBounceSellOrders(ctx context.Context, lastLow fixedpoint.Value, limitPrice fixedpoint.Value, currentPrice fixedpoint.Value, orderExecutor bbgo.OrderExecutor) {
|
func (s *Strategy) placeBounceSellOrders(ctx context.Context, resistancePrice fixedpoint.Value, orderExecutor bbgo.OrderExecutor) {
|
||||||
futuresMode := s.session.Futures || s.session.IsolatedFutures
|
futuresMode := s.session.Futures || s.session.IsolatedFutures
|
||||||
numLayers := fixedpoint.NewFromInt(int64(s.Entry.NumLayers))
|
totalQuantity := s.BounceShort.Quantity
|
||||||
d := s.Entry.CatBounceRatio.Div(numLayers)
|
numLayers := s.BounceShort.NumLayers
|
||||||
q := s.Entry.Quantity
|
if numLayers == 0 {
|
||||||
if !s.Entry.TotalQuantity.IsZero() {
|
numLayers = 1
|
||||||
q = s.Entry.TotalQuantity.Div(numLayers)
|
}
|
||||||
|
numLayersF := fixedpoint.NewFromInt(int64(numLayers))
|
||||||
|
|
||||||
|
layerSpread := s.BounceShort.LayerSpread
|
||||||
|
if layerSpread.IsZero() {
|
||||||
|
layerSpread = s.BounceShort.Ratio.Div(numLayersF)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < s.Entry.NumLayers; i++ {
|
quantity := totalQuantity.Div(numLayersF)
|
||||||
|
for i := 0; i < numLayers; i++ {
|
||||||
balances := s.session.GetAccount().Balances()
|
balances := s.session.GetAccount().Balances()
|
||||||
quoteBalance, _ := balances[s.Market.QuoteCurrency]
|
quoteBalance, _ := balances[s.Market.QuoteCurrency]
|
||||||
baseBalance, _ := balances[s.Market.BaseCurrency]
|
baseBalance, _ := balances[s.Market.BaseCurrency]
|
||||||
|
|
||||||
p := limitPrice.Mul(fixedpoint.One.Add(s.Entry.CatBounceRatio.Sub(fixedpoint.NewFromFloat(d.Float64() * float64(i)))))
|
// price = (resistance_price * (1.0 - ratio)) * ((1.0 + layerSpread) * i)
|
||||||
|
price := resistancePrice.Mul(
|
||||||
|
fixedpoint.One.Sub(s.BounceShort.Ratio)).Mul(
|
||||||
|
fixedpoint.One.Add(layerSpread).Mul(fixedpoint.NewFromInt(int64(i))))
|
||||||
|
|
||||||
if futuresMode {
|
if futuresMode {
|
||||||
if q.Mul(p).Compare(quoteBalance.Available) <= 0 {
|
if quantity.Mul(price).Compare(quoteBalance.Available) <= 0 {
|
||||||
s.placeOrder(ctx, lastLow, p, currentPrice, q, orderExecutor)
|
s.placeOrder(ctx, price, quantity, orderExecutor)
|
||||||
}
|
|
||||||
} else if s.Environment.IsBackTesting() {
|
|
||||||
if q.Compare(baseBalance.Available) <= 0 {
|
|
||||||
s.placeOrder(ctx, lastLow, p, currentPrice, q, orderExecutor)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if q.Compare(baseBalance.Available) <= 0 {
|
if quantity.Compare(baseBalance.Available) <= 0 {
|
||||||
s.placeOrder(ctx, lastLow, p, currentPrice, q, orderExecutor)
|
s.placeOrder(ctx, price, quantity, orderExecutor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) placeOrder(ctx context.Context, lastLow fixedpoint.Value, limitPrice fixedpoint.Value, currentPrice fixedpoint.Value, qty fixedpoint.Value, orderExecutor bbgo.OrderExecutor) {
|
func (s *Strategy) placeOrder(ctx context.Context, price fixedpoint.Value, quantity fixedpoint.Value, orderExecutor bbgo.OrderExecutor) {
|
||||||
submitOrder := types.SubmitOrder{
|
submitOrder := types.SubmitOrder{
|
||||||
Symbol: s.Symbol,
|
Symbol: s.Symbol,
|
||||||
Side: types.SideTypeSell,
|
Side: types.SideTypeSell,
|
||||||
Type: types.OrderTypeLimit,
|
Type: types.OrderTypeLimit,
|
||||||
Price: limitPrice,
|
Price: price,
|
||||||
Quantity: qty,
|
Quantity: quantity,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lastLow.IsZero() && lastLow.Compare(currentPrice) <= 0 {
|
|
||||||
submitOrder.Type = types.OrderTypeMarket
|
|
||||||
}
|
|
||||||
|
|
||||||
s.submitOrders(ctx, orderExecutor, submitOrder)
|
s.submitOrders(ctx, orderExecutor, submitOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) preloadPivot(pivot *indicator.Pivot, store *bbgo.MarketDataStore) *types.KLine {
|
||||||
|
klines, ok := store.KLinesOfInterval(pivot.Interval)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
last := (*klines)[len(*klines)-1]
|
||||||
|
log.Infof("last %s price: %f", s.Symbol, last.Close.Float64())
|
||||||
|
log.Debugf("updating pivot indicator: %d klines", len(*klines))
|
||||||
|
|
||||||
|
for i := pivot.Window; i < len(*klines); i++ {
|
||||||
|
pivot.Update((*klines)[0 : i+1])
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("found %s %v previous lows: %v", s.Symbol, pivot.IntervalWindow, pivot.Lows)
|
||||||
|
log.Infof("found %s %v previous highs: %v", s.Symbol, pivot.IntervalWindow, pivot.Highs)
|
||||||
|
return &last
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPossibleResistancePrices(closePrice float64, minDistance float64, lows []float64) []float64 {
|
||||||
|
// sort float64 in increasing order
|
||||||
|
sort.Float64s(lows)
|
||||||
|
log.Infof("sorted resistance lows: %+v", 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("possible resistance prices: %+v", resistancePrices)
|
||||||
|
return resistancePrices
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user