mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
optimize: drift strategy to use market trade signals
This commit is contained in:
parent
a15d125679
commit
ce86544c43
|
@ -1,102 +0,0 @@
|
||||||
---
|
|
||||||
persistence:
|
|
||||||
redis:
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 6379
|
|
||||||
db: 0
|
|
||||||
|
|
||||||
sessions:
|
|
||||||
binance:
|
|
||||||
exchange: binance
|
|
||||||
futures: false
|
|
||||||
envVarPrefix: binance
|
|
||||||
heikinAshi: false
|
|
||||||
|
|
||||||
# Drift strategy intends to place buy/sell orders as much value mas it could be. To exchanges that requires to
|
|
||||||
# calculate fees before placing limit orders (e.g. FTX Pro), make sure the fee rate is configured correctly and
|
|
||||||
# enable modifyOrderAmountForFee to prevent order rejection.
|
|
||||||
makerFeeRate: 0.0002
|
|
||||||
takerFeeRate: 0.0007
|
|
||||||
modifyOrderAmountForFee: false
|
|
||||||
|
|
||||||
exchangeStrategies:
|
|
||||||
|
|
||||||
- on: binance
|
|
||||||
drift:
|
|
||||||
canvasPath: "./output.png"
|
|
||||||
symbol: ETHBUSD
|
|
||||||
limitOrder: false
|
|
||||||
quantity: 0.01
|
|
||||||
# kline interval for indicators
|
|
||||||
interval: 1m
|
|
||||||
window: 1
|
|
||||||
useAtr: true
|
|
||||||
useStopLoss: true
|
|
||||||
stoploss: 0.23%
|
|
||||||
source: ohlc4
|
|
||||||
predictOffset: 2
|
|
||||||
noTrailingStopLoss: false
|
|
||||||
trailingStopLossType: kline
|
|
||||||
# stddev on high/low-source
|
|
||||||
hlVarianceMultiplier: 0.13
|
|
||||||
hlRangeWindow: 4
|
|
||||||
smootherWindow: 19
|
|
||||||
fisherTransformWindow: 73
|
|
||||||
atrWindow: 14
|
|
||||||
# orders not been traded will be canceled after `pendingMinutes` minutes
|
|
||||||
pendingMinutes: 5
|
|
||||||
noRebalance: true
|
|
||||||
trendWindow: 12
|
|
||||||
rebalanceFilter: 2
|
|
||||||
|
|
||||||
trailingActivationRatio: [0.0015, 0.002, 0.004, 0.01]
|
|
||||||
trailingCallbackRate: [0.0001, 0.00012, 0.001, 0.002]
|
|
||||||
|
|
||||||
generateGraph: true
|
|
||||||
graphPNLDeductFee: true
|
|
||||||
graphPNLPath: "./pnl.png"
|
|
||||||
graphCumPNLPath: "./cumpnl.png"
|
|
||||||
#exits:
|
|
||||||
#- roiStopLoss:
|
|
||||||
# percentage: 0.8%
|
|
||||||
#- roiTakeProfit:
|
|
||||||
# percentage: 35%
|
|
||||||
#- protectiveStopLoss:
|
|
||||||
# activationRatio: 0.6%
|
|
||||||
# stopLossRatio: 0.1%
|
|
||||||
# placeStopOrder: false
|
|
||||||
#- protectiveStopLoss:
|
|
||||||
# activationRatio: 5%
|
|
||||||
# stopLossRatio: 1%
|
|
||||||
# placeStopOrder: false
|
|
||||||
#- cumulatedVolumeTakeProfit:
|
|
||||||
# interval: 5m
|
|
||||||
# window: 2
|
|
||||||
# minQuoteVolume: 200_000_000
|
|
||||||
#- protectiveStopLoss:
|
|
||||||
# activationRatio: 2%
|
|
||||||
# stopLossRatio: 1%
|
|
||||||
# placeStopOrder: false
|
|
||||||
|
|
||||||
sync:
|
|
||||||
userDataStream:
|
|
||||||
trades: true
|
|
||||||
filledOrders: true
|
|
||||||
sessions:
|
|
||||||
- binance
|
|
||||||
symbols:
|
|
||||||
- ETHBUSD
|
|
||||||
|
|
||||||
backtest:
|
|
||||||
startTime: "2022-09-25"
|
|
||||||
endTime: "2022-09-30"
|
|
||||||
symbols:
|
|
||||||
- ETHBUSD
|
|
||||||
sessions: [binance]
|
|
||||||
accounts:
|
|
||||||
binance:
|
|
||||||
makerFeeRate: 0.0000
|
|
||||||
takerFeeRate: 0.0000
|
|
||||||
balances:
|
|
||||||
ETH: 0.03
|
|
||||||
BUSD: 0
|
|
|
@ -26,6 +26,7 @@ exchangeStrategies:
|
||||||
|
|
||||||
- on: binance
|
- on: binance
|
||||||
drift:
|
drift:
|
||||||
|
debug: false
|
||||||
minInterval: 1s
|
minInterval: 1s
|
||||||
limitOrder: true
|
limitOrder: true
|
||||||
#quantity: 0.0012
|
#quantity: 0.0012
|
||||||
|
@ -33,26 +34,24 @@ exchangeStrategies:
|
||||||
symbol: BTCUSDT
|
symbol: BTCUSDT
|
||||||
# kline interval for indicators
|
# kline interval for indicators
|
||||||
interval: 1s
|
interval: 1s
|
||||||
window: 6
|
window: 2
|
||||||
useAtr: true
|
useAtr: true
|
||||||
useStopLoss: true
|
useStopLoss: true
|
||||||
stoploss: 0.05%
|
stoploss: 0.01%
|
||||||
source: hl2
|
source: hl2
|
||||||
predictOffset: 2
|
predictOffset: 2
|
||||||
noTrailingStopLoss: false
|
noTrailingStopLoss: true
|
||||||
trailingStopLossType: kline
|
|
||||||
# stddev on high/low-source
|
# stddev on high/low-source
|
||||||
hlVarianceMultiplier: 0.14
|
hlVarianceMultiplier: 0.7
|
||||||
hlRangeWindow: 4
|
hlRangeWindow: 6
|
||||||
smootherWindow: 3
|
smootherWindow: 10
|
||||||
fisherTransformWindow: 125
|
fisherTransformWindow: 45
|
||||||
#fisherTransformWindow: 117
|
|
||||||
atrWindow: 24
|
atrWindow: 24
|
||||||
# orders not been traded will be canceled after `pendingMinutes` minutes
|
# orders not been traded will be canceled after `pendingMinutes` minutes
|
||||||
pendingMinutes: 10
|
pendingMinInterval: 6
|
||||||
noRebalance: true
|
noRebalance: true
|
||||||
trendWindow: 15
|
trendWindow: 4
|
||||||
rebalanceFilter: -0.1
|
rebalanceFilter: 2
|
||||||
|
|
||||||
# ActivationRatio should be increasing order
|
# ActivationRatio should be increasing order
|
||||||
# when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop
|
# when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop
|
||||||
|
@ -126,8 +125,8 @@ sync:
|
||||||
- BTCUSDT
|
- BTCUSDT
|
||||||
|
|
||||||
backtest:
|
backtest:
|
||||||
startTime: "2022-10-18"
|
startTime: "2022-10-19"
|
||||||
endTime: "2022-10-19"
|
endTime: "2022-10-20"
|
||||||
symbols:
|
symbols:
|
||||||
- BTCUSDT
|
- BTCUSDT
|
||||||
sessions: [binance]
|
sessions: [binance]
|
||||||
|
|
|
@ -66,57 +66,58 @@ type Strategy struct {
|
||||||
*types.TradeStats `persistence:"trade_stats"`
|
*types.TradeStats `persistence:"trade_stats"`
|
||||||
|
|
||||||
p *types.Position
|
p *types.Position
|
||||||
MinInterval types.Interval `json:"MinInterval"`
|
MinInterval types.Interval `json:"MinInterval"` // minimum interval referred for doing stoploss/trailing exists and updating highest/lowest
|
||||||
|
|
||||||
priceLines *types.Queue
|
priceLines *types.Queue
|
||||||
trendLine types.UpdatableSeriesExtend
|
trendLine types.UpdatableSeriesExtend
|
||||||
ma types.UpdatableSeriesExtend
|
ma types.UpdatableSeriesExtend
|
||||||
stdevHigh *indicator.StdDev
|
stdevHigh *indicator.StdDev
|
||||||
stdevLow *indicator.StdDev
|
stdevLow *indicator.StdDev
|
||||||
drift *DriftMA
|
drift *DriftMA
|
||||||
atr *indicator.ATR
|
atr *indicator.ATR
|
||||||
midPrice fixedpoint.Value
|
midPrice fixedpoint.Value // the midPrice is the average of bestBid and bestAsk in public orderbook
|
||||||
lock sync.RWMutex `ignore:"true"`
|
lock sync.RWMutex `ignore:"true"` // lock for midPrice
|
||||||
positionLock sync.RWMutex `ignore:"true"`
|
positionLock sync.RWMutex `ignore:"true"` // lock for highest/lowest and p
|
||||||
startTime time.Time
|
startTime time.Time // trading start time
|
||||||
counter int
|
counter int // number of MinInterval since startTime
|
||||||
orderPendingCounter map[uint64]int
|
maxCounterBuyCanceled int // the largest counter of the order on the buy side been cancelled. meaning the latest cancelled buy order.
|
||||||
frameKLine *types.KLine
|
maxCounterSellCanceled int // the largest counter of the order on the sell side been cancelled. meaning the latest cancelled sell order.
|
||||||
klineMin *types.KLine
|
orderPendingCounter map[uint64]int // records the timepoint when the orders are created, using the counter at the time.
|
||||||
|
frameKLine *types.KLine // last kline in Interval
|
||||||
|
klineMin *types.KLine // last kline in MinInterval
|
||||||
|
|
||||||
beta float64
|
beta float64 // last beta value from trendline's linear regression (previous slope of the trendline)
|
||||||
|
|
||||||
UseStopLoss bool `json:"useStopLoss" modifiable:"true"`
|
Debug bool `json:"debug" modifiable:"true"` // to print debug message or not
|
||||||
UseAtr bool `json:"useAtr" modifiable:"true"`
|
UseStopLoss bool `json:"useStopLoss" modifiable:"true"` // whether to use stoploss rate to do stoploss
|
||||||
StopLoss fixedpoint.Value `json:"stoploss" modifiable:"true"`
|
UseAtr bool `json:"useAtr" modifiable:"true"` // use atr as stoploss
|
||||||
CanvasPath string `json:"canvasPath"`
|
StopLoss fixedpoint.Value `json:"stoploss" modifiable:"true"` // stoploss rate
|
||||||
PredictOffset int `json:"predictOffset"`
|
PredictOffset int `json:"predictOffset"` // the lookback length for the prediction using linear regression
|
||||||
HighLowVarianceMultiplier float64 `json:"hlVarianceMultiplier" modifiable:"true"`
|
HighLowVarianceMultiplier float64 `json:"hlVarianceMultiplier" modifiable:"true"` // modifier to set the limit order price
|
||||||
NoTrailingStopLoss bool `json:"noTrailingStopLoss" modifiable:"true"`
|
NoTrailingStopLoss bool `json:"noTrailingStopLoss" modifiable:"true"` // turn off the trailing exit and stoploss
|
||||||
TrailingStopLossType string `json:"trailingStopLossType" modifiable:"true"` // trailing stop sources. Possible options are `kline` for 1m kline and `realtime` from order updates
|
HLRangeWindow int `json:"hlRangeWindow"` // ma window for kline high/low changes
|
||||||
HLRangeWindow int `json:"hlRangeWindow"`
|
SmootherWindow int `json:"smootherWindow"` // window that controls the smoothness of drift
|
||||||
SmootherWindow int `json:"smootherWindow"`
|
FisherTransformWindow int `json:"fisherTransformWindow"` // fisher transform window to filter drift's negative signals
|
||||||
FisherTransformWindow int `json:"fisherTransformWindow"`
|
ATRWindow int `json:"atrWindow"` // window for atr indicator
|
||||||
ATRWindow int `json:"atrWindow"`
|
PendingMinInterval int `json:"pendingMinInterval" modifiable:"true"` // if order not be traded for pendingMinInterval of time, cancel it.
|
||||||
PendingMinInterval int `json:"pendingMinInterval" modifiable:"true"` // if order not be traded for pendingMinInterval of time, cancel it.
|
NoRebalance bool `json:"noRebalance" modifiable:"true"` // disable rebalance
|
||||||
NoRebalance bool `json:"noRebalance" modifiable:"true"` // disable rebalance
|
TrendWindow int `json:"trendWindow"` // trendLine is used for rebalancing the position. When trendLine goes up, hold base, otherwise hold quote
|
||||||
TrendWindow int `json:"trendWindow"` // trendLine is used for rebalancing the position. When trendLine goes up, hold base, otherwise hold quote
|
RebalanceFilter float64 `json:"rebalanceFilter" modifiable:"true"` // beta filter on the Linear Regression of trendLine
|
||||||
RebalanceFilter float64 `json:"rebalanceFilter" modifiable:"true"` // beta filter on the Linear Regression of trendLine
|
|
||||||
TrailingCallbackRate []float64 `json:"trailingCallbackRate" modifiable:"true"`
|
TrailingCallbackRate []float64 `json:"trailingCallbackRate" modifiable:"true"`
|
||||||
TrailingActivationRatio []float64 `json:"trailingActivationRatio" modifiable:"true"`
|
TrailingActivationRatio []float64 `json:"trailingActivationRatio" modifiable:"true"`
|
||||||
|
|
||||||
buyPrice float64 `persistence:"buy_price"`
|
buyPrice float64 `persistence:"buy_price"` // price when a long position is opened
|
||||||
sellPrice float64 `persistence:"sell_price"`
|
sellPrice float64 `persistence:"sell_price"` // price when a short position is opened
|
||||||
highestPrice float64 `persistence:"highest_price"`
|
highestPrice float64 `persistence:"highest_price"` // highestPrice when the position is opened
|
||||||
lowestPrice float64 `persistence:"lowest_price"`
|
lowestPrice float64 `persistence:"lowest_price"` // lowestPrice when the position is opened
|
||||||
|
|
||||||
// This is not related to trade but for statistics graph generation
|
// This is not related to trade but for statistics graph generation
|
||||||
// Will deduct fee in percentage from every trade
|
// Will deduct fee in percentage from every trade
|
||||||
GraphPNLDeductFee bool `json:"graphPNLDeductFee"`
|
GraphPNLDeductFee bool `json:"graphPNLDeductFee"`
|
||||||
GraphPNLPath string `json:"graphPNLPath"`
|
CanvasPath string `json:"canvasPath"` // backtest related. the path to store the indicator graph
|
||||||
GraphCumPNLPath string `json:"graphCumPNLPath"`
|
GraphPNLPath string `json:"graphPNLPath"` // backtest related. the path to store the pnl % graph per trade graph.
|
||||||
// Whether to generate graph when shutdown
|
GraphCumPNLPath string `json:"graphCumPNLPath"` // backtest related. the path to store the asset changes in graph
|
||||||
GenerateGraph bool `json:"generateGraph"`
|
GenerateGraph bool `json:"generateGraph"` // whether to generate graph when shutdown
|
||||||
|
|
||||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||||
Session *bbgo.ExchangeSession
|
Session *bbgo.ExchangeSession
|
||||||
|
@ -139,7 +140,7 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
|
|
||||||
if !bbgo.IsBackTesting {
|
if !bbgo.IsBackTesting {
|
||||||
session.Subscribe(types.BookTickerChannel, s.Symbol, types.SubscribeOptions{})
|
session.Subscribe(types.BookTickerChannel, s.Symbol, types.SubscribeOptions{})
|
||||||
session.Subscribe(types.AggTradeChannel, s.Symbol, types.SubscribeOptions{})
|
session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{})
|
||||||
// able to preload
|
// able to preload
|
||||||
if s.MinInterval.Milliseconds() >= types.Interval1s.Milliseconds() && s.MinInterval.Milliseconds()%types.Interval1s.Milliseconds() == 0 {
|
if s.MinInterval.Milliseconds() >= types.Interval1s.Milliseconds() && s.MinInterval.Milliseconds()%types.Interval1s.Milliseconds() == 0 {
|
||||||
maxWindow := (s.Window + s.SmootherWindow + s.FisherTransformWindow) * (s.Interval.Milliseconds() / s.MinInterval.Milliseconds())
|
maxWindow := (s.Window + s.SmootherWindow + s.FisherTransformWindow) * (s.Interval.Milliseconds() / s.MinInterval.Milliseconds())
|
||||||
|
@ -296,6 +297,15 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64) (int, e
|
||||||
err := s.GeneralOrderExecutor.CancelNoWait(ctx)
|
err := s.GeneralOrderExecutor.CancelNoWait(ctx)
|
||||||
// TODO: clean orderPendingCounter on cancel/trade
|
// TODO: clean orderPendingCounter on cancel/trade
|
||||||
for _, order := range nonTraded {
|
for _, order := range nonTraded {
|
||||||
|
if order.Side == types.SideTypeSell {
|
||||||
|
if s.maxCounterSellCanceled < s.orderPendingCounter[order.OrderID] {
|
||||||
|
s.maxCounterSellCanceled = s.orderPendingCounter[order.OrderID]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if s.maxCounterBuyCanceled < s.orderPendingCounter[order.OrderID] {
|
||||||
|
s.maxCounterBuyCanceled = s.orderPendingCounter[order.OrderID]
|
||||||
|
}
|
||||||
|
}
|
||||||
delete(s.orderPendingCounter, order.OrderID)
|
delete(s.orderPendingCounter, order.OrderID)
|
||||||
}
|
}
|
||||||
log.Warnf("cancel all %v", err)
|
log.Warnf("cancel all %v", err)
|
||||||
|
@ -346,7 +356,6 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
|
||||||
bestBid := ticker.Buy
|
bestBid := ticker.Buy
|
||||||
bestAsk := ticker.Sell
|
bestAsk := ticker.Sell
|
||||||
|
|
||||||
var pricef float64
|
|
||||||
if !util.TryLock(&s.lock) {
|
if !util.TryLock(&s.lock) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -357,37 +366,10 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
|
||||||
} else {
|
} else {
|
||||||
s.midPrice = bestBid
|
s.midPrice = bestBid
|
||||||
}
|
}
|
||||||
pricef = s.midPrice.Float64()
|
|
||||||
|
|
||||||
s.lock.Unlock()
|
s.lock.Unlock()
|
||||||
|
|
||||||
if !util.TryLock(&s.positionLock) {
|
// we removed realtime stoploss and trailingStop.
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.highestPrice > 0 && s.highestPrice < pricef {
|
|
||||||
s.highestPrice = pricef
|
|
||||||
}
|
|
||||||
if s.lowestPrice > 0 && s.lowestPrice > pricef {
|
|
||||||
s.lowestPrice = pricef
|
|
||||||
}
|
|
||||||
if s.CheckStopLoss() {
|
|
||||||
s.positionLock.Unlock()
|
|
||||||
s.ClosePosition(ctx, fixedpoint.One)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// for trailing stoploss during the realtime
|
|
||||||
if s.NoTrailingStopLoss || s.TrailingStopLossType == "kline" {
|
|
||||||
s.positionLock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
exitCondition := s.trailingCheck(pricef, "short") || s.trailingCheck(pricef, "long")
|
|
||||||
|
|
||||||
s.positionLock.Unlock()
|
|
||||||
if exitCondition {
|
|
||||||
s.ClosePosition(ctx, fixedpoint.One)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
s.getLastPrice = func() (lastPrice fixedpoint.Value) {
|
s.getLastPrice = func() (lastPrice fixedpoint.Value) {
|
||||||
var ok bool
|
var ok bool
|
||||||
|
@ -585,35 +567,29 @@ func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine) {
|
||||||
if s.highestPrice > 0 && highf > s.highestPrice {
|
if s.highestPrice > 0 && highf > s.highestPrice {
|
||||||
s.highestPrice = highf
|
s.highestPrice = highf
|
||||||
}
|
}
|
||||||
|
s.positionLock.Unlock()
|
||||||
|
|
||||||
numPending := 0
|
numPending := 0
|
||||||
var err error
|
var err error
|
||||||
if numPending, err = s.smartCancel(ctx, pricef, atr); err != nil {
|
if numPending, err = s.smartCancel(ctx, pricef, atr); err != nil {
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
s.positionLock.Unlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if numPending > 0 {
|
if numPending > 0 {
|
||||||
s.positionLock.Unlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.NoTrailingStopLoss || s.TrailingStopLossType == "realtime" {
|
if s.NoTrailingStopLoss {
|
||||||
s.positionLock.Unlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
exitCondition := s.CheckStopLoss() || s.trailingCheck(highf, "short") || s.trailingCheck(lowf, "long")
|
exitCondition := s.CheckStopLoss() || s.trailingCheck(highf, "short") || s.trailingCheck(lowf, "long")
|
||||||
s.positionLock.Unlock()
|
|
||||||
if exitCondition {
|
if exitCondition {
|
||||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
var driftPred, atr float64
|
|
||||||
var drift []float64
|
|
||||||
|
|
||||||
s.frameKLine.Set(&kline)
|
s.frameKLine.Set(&kline)
|
||||||
|
|
||||||
source := s.GetSource(&kline)
|
source := s.GetSource(&kline)
|
||||||
|
@ -625,9 +601,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
|
|
||||||
s.atr.PushK(kline)
|
s.atr.PushK(kline)
|
||||||
|
|
||||||
driftPred = s.drift.Predict(s.PredictOffset)
|
atr := s.atr.Last()
|
||||||
ddriftPred := s.drift.drift.Predict(s.PredictOffset)
|
|
||||||
atr = s.atr.Last()
|
|
||||||
price := s.getLastPrice()
|
price := s.getLastPrice()
|
||||||
pricef := price.Float64()
|
pricef := price.Float64()
|
||||||
lowf := math.Min(kline.Low.Float64(), pricef)
|
lowf := math.Min(kline.Low.Float64(), pricef)
|
||||||
|
@ -636,7 +610,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
s.stdevLow.Update(lowdiff)
|
s.stdevLow.Update(lowdiff)
|
||||||
highdiff := highf - s.ma.Last()
|
highdiff := highf - s.ma.Last()
|
||||||
s.stdevHigh.Update(highdiff)
|
s.stdevHigh.Update(highdiff)
|
||||||
drift = s.drift.Array(2)
|
drift := s.drift.Array(2)
|
||||||
if len(drift) < 2 || len(drift) < s.PredictOffset {
|
if len(drift) < 2 || len(drift) < s.PredictOffset {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -649,77 +623,78 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.positionLock.Lock()
|
|
||||||
log.Infof("highdiff: %3.2f ma: %.2f, open: %8v, close: %8v, high: %8v, low: %8v, time: %v %v", s.stdevHigh.Last(), s.ma.Last(), kline.Open, kline.Close, kline.High, kline.Low, kline.StartTime, kline.EndTime)
|
log.Infof("highdiff: %3.2f ma: %.2f, open: %8v, close: %8v, high: %8v, low: %8v, time: %v %v", s.stdevHigh.Last(), s.ma.Last(), kline.Open, kline.Close, kline.High, kline.Low, kline.StartTime, kline.EndTime)
|
||||||
|
|
||||||
|
s.positionLock.Lock()
|
||||||
if s.lowestPrice > 0 && lowf < s.lowestPrice {
|
if s.lowestPrice > 0 && lowf < s.lowestPrice {
|
||||||
s.lowestPrice = lowf
|
s.lowestPrice = lowf
|
||||||
}
|
}
|
||||||
if s.highestPrice > 0 && highf > s.highestPrice {
|
if s.highestPrice > 0 && highf > s.highestPrice {
|
||||||
s.highestPrice = highf
|
s.highestPrice = highf
|
||||||
}
|
}
|
||||||
|
s.positionLock.Unlock()
|
||||||
|
|
||||||
if !s.NoRebalance {
|
if !s.NoRebalance {
|
||||||
s.Rebalance(ctx)
|
s.Rebalance(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
balances := s.GeneralOrderExecutor.Session().GetAccount().Balances()
|
if s.Debug {
|
||||||
bbgo.Notify("source: %.4f, price: %.4f, driftPred: %.4f, ddriftPred: %.4f, drift[1]: %.4f, ddrift[1]: %.4f, atr: %.4f, lowf %.4f, highf: %.4f lowest: %.4f highest: %.4f sp %.4f bp %.4f",
|
balances := s.GeneralOrderExecutor.Session().GetAccount().Balances()
|
||||||
sourcef, pricef, driftPred, ddriftPred, drift[1], ddrift[1], atr, lowf, highf, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice)
|
bbgo.Notify("source: %.4f, price: %.4f, drift[0]: %.4f, ddrift[0]: %.4f, lowf %.4f, highf: %.4f lowest: %.4f highest: %.4f sp %.4f bp %.4f",
|
||||||
// Notify will parse args to strings and process separately
|
sourcef, pricef, drift[0], ddrift[0], atr, lowf, highf, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice)
|
||||||
bbgo.Notify("balances: [Total] %v %s [Base] %s(%v %s) [Quote] %s",
|
// Notify will parse args to strings and process separately
|
||||||
s.CalcAssetValue(price),
|
bbgo.Notify("balances: [Total] %v %s [Base] %s(%v %s) [Quote] %s",
|
||||||
s.Market.QuoteCurrency,
|
s.CalcAssetValue(price),
|
||||||
balances[s.Market.BaseCurrency].String(),
|
s.Market.QuoteCurrency,
|
||||||
balances[s.Market.BaseCurrency].Total().Mul(price),
|
balances[s.Market.BaseCurrency].String(),
|
||||||
s.Market.QuoteCurrency,
|
balances[s.Market.BaseCurrency].Total().Mul(price),
|
||||||
balances[s.Market.QuoteCurrency].String(),
|
s.Market.QuoteCurrency,
|
||||||
)
|
balances[s.Market.QuoteCurrency].String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
shortCondition := drift[1] >= 0 && drift[0] <= 0 || (drift[1] >= drift[0] && drift[1] <= 0) || ddrift[1] >= 0 && ddrift[0] <= 0 || (ddrift[1] >= ddrift[0] && ddrift[1] <= 0)
|
shortCondition := drift[1] >= 0 && drift[0] <= 0 || (drift[1] >= drift[0] && drift[1] <= 0) || ddrift[1] >= 0 && ddrift[0] <= 0 || (ddrift[1] >= ddrift[0] && ddrift[1] <= 0)
|
||||||
longCondition := drift[1] <= 0 && drift[0] >= 0 || (drift[1] <= drift[0] && drift[1] >= 0) || ddrift[1] <= 0 && ddrift[0] >= 0 || (ddrift[1] <= ddrift[0] && ddrift[1] >= 0)
|
longCondition := drift[1] <= 0 && drift[0] >= 0 || (drift[1] <= drift[0] && drift[1] >= 0) || ddrift[1] <= 0 && ddrift[0] >= 0 || (ddrift[1] <= ddrift[0] && ddrift[1] >= 0)
|
||||||
if shortCondition && longCondition {
|
if shortCondition && longCondition {
|
||||||
if drift[1] > drift[0] {
|
if s.priceLines.Index(1) > s.priceLines.Last() {
|
||||||
longCondition = false
|
longCondition = false
|
||||||
} else {
|
} else {
|
||||||
shortCondition = false
|
shortCondition = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exitCondition := s.CheckStopLoss() || s.trailingCheck(pricef, "short") || s.trailingCheck(pricef, "long")
|
exitCondition := !s.NoTrailingStopLoss && (s.CheckStopLoss() || s.trailingCheck(pricef, "short") || s.trailingCheck(pricef, "long"))
|
||||||
|
|
||||||
if exitCondition {
|
if exitCondition || longCondition || shortCondition {
|
||||||
s.positionLock.Unlock()
|
var err error
|
||||||
if err := s.GeneralOrderExecutor.CancelNoWait(ctx); err != nil {
|
var hold int
|
||||||
|
if hold, err = s.smartCancel(ctx, sourcef, atr); err != nil {
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
|
}
|
||||||
|
if hold > 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
} else {
|
||||||
if shortCondition || longCondition {
|
if _, err := s.smartCancel(ctx, sourcef, atr); err != nil {
|
||||||
s.positionLock.Lock()
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if longCondition {
|
if longCondition {
|
||||||
if err := s.GeneralOrderExecutor.CancelNoWait(ctx); err != nil {
|
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
|
||||||
s.positionLock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
source = source.Sub(fixedpoint.NewFromFloat(s.stdevLow.Last() * s.HighLowVarianceMultiplier))
|
source = source.Sub(fixedpoint.NewFromFloat(s.stdevLow.Last() * s.HighLowVarianceMultiplier))
|
||||||
if source.Compare(price) > 0 {
|
if source.Compare(price) > 0 {
|
||||||
source = price
|
source = price
|
||||||
}
|
}
|
||||||
/*source = fixedpoint.NewFromFloat(s.ma.Last() - s.stdevLow.Last()*s.HighLowVarianceMultiplier)
|
|
||||||
if source.Compare(price) > 0 {
|
|
||||||
source = price
|
|
||||||
}
|
|
||||||
sourcef = source.Float64()*/
|
|
||||||
log.Infof("source in long %v %v %f", source, price, s.stdevLow.Last())
|
log.Infof("source in long %v %v %f", source, price, s.stdevLow.Last())
|
||||||
|
|
||||||
s.positionLock.Unlock()
|
|
||||||
opt := s.OpenPositionOptions
|
opt := s.OpenPositionOptions
|
||||||
opt.Long = true
|
opt.Long = true
|
||||||
|
opt.LimitOrder = true
|
||||||
|
// force to use market taker
|
||||||
|
if s.counter-s.maxCounterBuyCanceled <= 1 {
|
||||||
|
opt.LimitOrder = false
|
||||||
|
}
|
||||||
opt.Price = source
|
opt.Price = source
|
||||||
opt.Tags = []string{"long"}
|
opt.Tags = []string{"long"}
|
||||||
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
|
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
|
||||||
|
@ -738,28 +713,20 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if shortCondition {
|
if shortCondition {
|
||||||
if err := s.GeneralOrderExecutor.CancelNoWait(ctx); err != nil {
|
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
|
||||||
s.positionLock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
source = source.Add(fixedpoint.NewFromFloat(s.stdevHigh.Last() * s.HighLowVarianceMultiplier))
|
source = source.Add(fixedpoint.NewFromFloat(s.stdevHigh.Last() * s.HighLowVarianceMultiplier))
|
||||||
if source.Compare(price) < 0 {
|
if source.Compare(price) < 0 {
|
||||||
source = price
|
source = price
|
||||||
}
|
}
|
||||||
/*source = fixedpoint.NewFromFloat(s.ma.Last() + s.stdevHigh.Last()*s.HighLowVarianceMultiplier)
|
|
||||||
if source.Compare(price) < 0 {
|
|
||||||
source = price
|
|
||||||
}
|
|
||||||
sourcef = source.Float64()*/
|
|
||||||
|
|
||||||
log.Infof("source in short: %v", source)
|
log.Infof("source in short: %v", source)
|
||||||
|
|
||||||
s.positionLock.Unlock()
|
|
||||||
opt := s.OpenPositionOptions
|
opt := s.OpenPositionOptions
|
||||||
opt.Short = true
|
opt.Short = true
|
||||||
opt.Price = source
|
opt.Price = source
|
||||||
|
opt.LimitOrder = true
|
||||||
|
if s.counter-s.maxCounterSellCanceled <= 1 {
|
||||||
|
opt.LimitOrder = false
|
||||||
|
}
|
||||||
opt.Tags = []string{"short"}
|
opt.Tags = []string{"short"}
|
||||||
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
|
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -775,7 +742,6 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.positionLock.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||||
|
@ -842,13 +808,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
}
|
}
|
||||||
s.GeneralOrderExecutor.TradeCollector().OnTrade(func(trade types.Trade, _profit, _netProfit fixedpoint.Value) {
|
s.GeneralOrderExecutor.TradeCollector().OnTrade(func(trade types.Trade, _profit, _netProfit fixedpoint.Value) {
|
||||||
s.p.AddTrade(trade)
|
s.p.AddTrade(trade)
|
||||||
order, ok := s.GeneralOrderExecutor.TradeCollector().OrderStore().Get(trade.OrderID)
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Sprintf("cannot find order: %v", trade))
|
|
||||||
}
|
|
||||||
tag := order.Tag
|
|
||||||
|
|
||||||
price := trade.Price.Float64()
|
price := trade.Price.Float64()
|
||||||
|
delete(s.orderPendingCounter, trade.OrderID)
|
||||||
|
|
||||||
if s.buyPrice > 0 {
|
if s.buyPrice > 0 {
|
||||||
profit.Update(modify(price / s.buyPrice))
|
profit.Update(modify(price / s.buyPrice))
|
||||||
|
@ -858,19 +819,18 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
cumProfit.Update(s.CalcAssetValue(trade.Price).Float64())
|
cumProfit.Update(s.CalcAssetValue(trade.Price).Float64())
|
||||||
}
|
}
|
||||||
s.positionLock.Lock()
|
s.positionLock.Lock()
|
||||||
defer s.positionLock.Unlock()
|
|
||||||
if s.p.IsDust(trade.Price) {
|
if s.p.IsDust(trade.Price) {
|
||||||
s.buyPrice = 0
|
s.buyPrice = 0
|
||||||
s.sellPrice = 0
|
s.sellPrice = 0
|
||||||
s.highestPrice = 0
|
s.highestPrice = 0
|
||||||
s.lowestPrice = 0
|
s.lowestPrice = 0
|
||||||
} else if s.p.IsLong() {
|
} else if s.p.IsLong() {
|
||||||
s.buyPrice = s.p.ApproximateAverageCost.Float64() // trade.Price.Float64()
|
s.buyPrice = s.p.ApproximateAverageCost.Float64()
|
||||||
s.sellPrice = 0
|
s.sellPrice = 0
|
||||||
s.highestPrice = math.Max(s.buyPrice, s.highestPrice)
|
s.highestPrice = math.Max(s.buyPrice, s.highestPrice)
|
||||||
s.lowestPrice = s.buyPrice
|
s.lowestPrice = s.buyPrice
|
||||||
} else if s.p.IsShort() {
|
} else if s.p.IsShort() {
|
||||||
s.sellPrice = s.p.ApproximateAverageCost.Float64() // trade.Price.Float64()
|
s.sellPrice = s.p.ApproximateAverageCost.Float64()
|
||||||
s.buyPrice = 0
|
s.buyPrice = 0
|
||||||
s.highestPrice = s.sellPrice
|
s.highestPrice = s.sellPrice
|
||||||
if s.lowestPrice == 0 {
|
if s.lowestPrice == 0 {
|
||||||
|
@ -879,7 +839,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.lowestPrice = math.Min(s.lowestPrice, s.sellPrice)
|
s.lowestPrice = math.Min(s.lowestPrice, s.sellPrice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bbgo.Notify("tag: %s, sp: %.4f bp: %.4f hp: %.4f lp: %.4f, trade: %s, pos: %s", tag, s.sellPrice, s.buyPrice, s.highestPrice, s.lowestPrice, trade.String(), s.p.String())
|
s.positionLock.Unlock()
|
||||||
})
|
})
|
||||||
|
|
||||||
s.frameKLine = &types.KLine{}
|
s.frameKLine = &types.KLine{}
|
||||||
|
@ -891,42 +851,43 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1d, s.startTime))
|
s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1d, s.startTime))
|
||||||
s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1w, s.startTime))
|
s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1w, s.startTime))
|
||||||
|
|
||||||
// default value: use 1m kline
|
|
||||||
if !s.NoTrailingStopLoss && s.IsBackTesting() || s.TrailingStopLossType == "" {
|
|
||||||
s.TrailingStopLossType = "kline"
|
|
||||||
}
|
|
||||||
|
|
||||||
bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) {
|
bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) {
|
||||||
canvas := s.DrawIndicators(s.frameKLine.StartTime)
|
go func() {
|
||||||
var buffer bytes.Buffer
|
canvas := s.DrawIndicators(s.frameKLine.StartTime)
|
||||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
var buffer bytes.Buffer
|
||||||
log.WithError(err).Errorf("cannot render indicators in drift")
|
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||||
reply.Message(fmt.Sprintf("[error] cannot render indicators in drift: %v", err))
|
log.WithError(err).Errorf("cannot render indicators in drift")
|
||||||
return
|
reply.Message(fmt.Sprintf("[error] cannot render indicators in drift: %v", err))
|
||||||
}
|
return
|
||||||
bbgo.SendPhoto(&buffer)
|
}
|
||||||
|
bbgo.SendPhoto(&buffer)
|
||||||
|
}()
|
||||||
})
|
})
|
||||||
|
|
||||||
bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) {
|
bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) {
|
||||||
canvas := s.DrawPNL(&profit)
|
go func() {
|
||||||
var buffer bytes.Buffer
|
canvas := s.DrawPNL(&profit)
|
||||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
var buffer bytes.Buffer
|
||||||
log.WithError(err).Errorf("cannot render pnl in drift")
|
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||||
reply.Message(fmt.Sprintf("[error] cannot render pnl in drift: %v", err))
|
log.WithError(err).Errorf("cannot render pnl in drift")
|
||||||
return
|
reply.Message(fmt.Sprintf("[error] cannot render pnl in drift: %v", err))
|
||||||
}
|
return
|
||||||
bbgo.SendPhoto(&buffer)
|
}
|
||||||
|
bbgo.SendPhoto(&buffer)
|
||||||
|
}()
|
||||||
})
|
})
|
||||||
|
|
||||||
bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) {
|
bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) {
|
||||||
canvas := s.DrawCumPNL(&cumProfit)
|
go func() {
|
||||||
var buffer bytes.Buffer
|
canvas := s.DrawCumPNL(&cumProfit)
|
||||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
var buffer bytes.Buffer
|
||||||
log.WithError(err).Errorf("cannot render cumpnl in drift")
|
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||||
reply.Message(fmt.Sprintf("[error] canot render cumpnl in drift: %v", err))
|
log.WithError(err).Errorf("cannot render cumpnl in drift")
|
||||||
return
|
reply.Message(fmt.Sprintf("[error] canot render cumpnl in drift: %v", err))
|
||||||
}
|
return
|
||||||
bbgo.SendPhoto(&buffer)
|
}
|
||||||
|
bbgo.SendPhoto(&buffer)
|
||||||
|
}()
|
||||||
})
|
})
|
||||||
|
|
||||||
bbgo.RegisterCommand("/config", "Show latest config", func(reply interact.Reply) {
|
bbgo.RegisterCommand("/config", "Show latest config", func(reply interact.Reply) {
|
||||||
|
@ -965,7 +926,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
}
|
}
|
||||||
|
|
||||||
store.OnKLineClosed(func(kline types.KLine) {
|
store.OnKLineClosed(func(kline types.KLine) {
|
||||||
s.counter = int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Milliseconds())
|
s.counter = int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Milliseconds()) / s.MinInterval.Milliseconds()
|
||||||
if kline.Interval == s.Interval {
|
if kline.Interval == s.Interval {
|
||||||
s.klineHandler(ctx, kline)
|
s.klineHandler(ctx, kline)
|
||||||
} else if kline.Interval == s.MinInterval {
|
} else if kline.Interval == s.MinInterval {
|
||||||
|
|
|
@ -104,7 +104,7 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
// this is not enough if we're subscribing 30m intervals using SerialMarketDataStore
|
// this is not enough if we're subscribing 30m intervals using SerialMarketDataStore
|
||||||
if !bbgo.IsBackTesting {
|
if !bbgo.IsBackTesting {
|
||||||
session.Subscribe(types.BookTickerChannel, s.Symbol, types.SubscribeOptions{})
|
session.Subscribe(types.BookTickerChannel, s.Symbol, types.SubscribeOptions{})
|
||||||
session.Subscribe(types.AggTradeChannel, s.Symbol, types.SubscribeOptions{})
|
session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{})
|
||||||
if s.MinInterval.Milliseconds() >= types.Interval1s.Milliseconds() && s.MinInterval.Milliseconds()%types.Interval1s.Milliseconds() == 0 {
|
if s.MinInterval.Milliseconds() >= types.Interval1s.Milliseconds() && s.MinInterval.Milliseconds()%types.Interval1s.Milliseconds() == 0 {
|
||||||
bbgo.KLinePreloadLimit = int64(((s.Interval.Milliseconds()/s.MinInterval.Milliseconds())*s.WindowSlow/1000 + 1) + 1000)
|
bbgo.KLinePreloadLimit = int64(((s.Interval.Milliseconds()/s.MinInterval.Milliseconds())*s.WindowSlow/1000 + 1) + 1000)
|
||||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
|
||||||
|
|
Loading…
Reference in New Issue
Block a user