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
|
||||
drift:
|
||||
debug: false
|
||||
minInterval: 1s
|
||||
limitOrder: true
|
||||
#quantity: 0.0012
|
||||
|
@ -33,26 +34,24 @@ exchangeStrategies:
|
|||
symbol: BTCUSDT
|
||||
# kline interval for indicators
|
||||
interval: 1s
|
||||
window: 6
|
||||
window: 2
|
||||
useAtr: true
|
||||
useStopLoss: true
|
||||
stoploss: 0.05%
|
||||
stoploss: 0.01%
|
||||
source: hl2
|
||||
predictOffset: 2
|
||||
noTrailingStopLoss: false
|
||||
trailingStopLossType: kline
|
||||
noTrailingStopLoss: true
|
||||
# stddev on high/low-source
|
||||
hlVarianceMultiplier: 0.14
|
||||
hlRangeWindow: 4
|
||||
smootherWindow: 3
|
||||
fisherTransformWindow: 125
|
||||
#fisherTransformWindow: 117
|
||||
hlVarianceMultiplier: 0.7
|
||||
hlRangeWindow: 6
|
||||
smootherWindow: 10
|
||||
fisherTransformWindow: 45
|
||||
atrWindow: 24
|
||||
# orders not been traded will be canceled after `pendingMinutes` minutes
|
||||
pendingMinutes: 10
|
||||
pendingMinInterval: 6
|
||||
noRebalance: true
|
||||
trendWindow: 15
|
||||
rebalanceFilter: -0.1
|
||||
trendWindow: 4
|
||||
rebalanceFilter: 2
|
||||
|
||||
# ActivationRatio should be increasing order
|
||||
# when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop
|
||||
|
@ -126,8 +125,8 @@ sync:
|
|||
- BTCUSDT
|
||||
|
||||
backtest:
|
||||
startTime: "2022-10-18"
|
||||
endTime: "2022-10-19"
|
||||
startTime: "2022-10-19"
|
||||
endTime: "2022-10-20"
|
||||
symbols:
|
||||
- BTCUSDT
|
||||
sessions: [binance]
|
||||
|
|
|
@ -66,57 +66,58 @@ type Strategy struct {
|
|||
*types.TradeStats `persistence:"trade_stats"`
|
||||
|
||||
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
|
||||
trendLine types.UpdatableSeriesExtend
|
||||
ma types.UpdatableSeriesExtend
|
||||
stdevHigh *indicator.StdDev
|
||||
stdevLow *indicator.StdDev
|
||||
drift *DriftMA
|
||||
atr *indicator.ATR
|
||||
midPrice fixedpoint.Value
|
||||
lock sync.RWMutex `ignore:"true"`
|
||||
positionLock sync.RWMutex `ignore:"true"`
|
||||
startTime time.Time
|
||||
counter int
|
||||
orderPendingCounter map[uint64]int
|
||||
frameKLine *types.KLine
|
||||
klineMin *types.KLine
|
||||
priceLines *types.Queue
|
||||
trendLine types.UpdatableSeriesExtend
|
||||
ma types.UpdatableSeriesExtend
|
||||
stdevHigh *indicator.StdDev
|
||||
stdevLow *indicator.StdDev
|
||||
drift *DriftMA
|
||||
atr *indicator.ATR
|
||||
midPrice fixedpoint.Value // the midPrice is the average of bestBid and bestAsk in public orderbook
|
||||
lock sync.RWMutex `ignore:"true"` // lock for midPrice
|
||||
positionLock sync.RWMutex `ignore:"true"` // lock for highest/lowest and p
|
||||
startTime time.Time // trading start time
|
||||
counter int // number of MinInterval since startTime
|
||||
maxCounterBuyCanceled int // the largest counter of the order on the buy side been cancelled. meaning the latest cancelled buy order.
|
||||
maxCounterSellCanceled int // the largest counter of the order on the sell side been cancelled. meaning the latest cancelled sell order.
|
||||
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"`
|
||||
UseAtr bool `json:"useAtr" modifiable:"true"`
|
||||
StopLoss fixedpoint.Value `json:"stoploss" modifiable:"true"`
|
||||
CanvasPath string `json:"canvasPath"`
|
||||
PredictOffset int `json:"predictOffset"`
|
||||
HighLowVarianceMultiplier float64 `json:"hlVarianceMultiplier" modifiable:"true"`
|
||||
NoTrailingStopLoss bool `json:"noTrailingStopLoss" modifiable:"true"`
|
||||
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"`
|
||||
SmootherWindow int `json:"smootherWindow"`
|
||||
FisherTransformWindow int `json:"fisherTransformWindow"`
|
||||
ATRWindow int `json:"atrWindow"`
|
||||
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
|
||||
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
|
||||
Debug bool `json:"debug" modifiable:"true"` // to print debug message or not
|
||||
UseStopLoss bool `json:"useStopLoss" modifiable:"true"` // whether to use stoploss rate to do stoploss
|
||||
UseAtr bool `json:"useAtr" modifiable:"true"` // use atr as stoploss
|
||||
StopLoss fixedpoint.Value `json:"stoploss" modifiable:"true"` // stoploss rate
|
||||
PredictOffset int `json:"predictOffset"` // the lookback length for the prediction using linear regression
|
||||
HighLowVarianceMultiplier float64 `json:"hlVarianceMultiplier" modifiable:"true"` // modifier to set the limit order price
|
||||
NoTrailingStopLoss bool `json:"noTrailingStopLoss" modifiable:"true"` // turn off the trailing exit and stoploss
|
||||
HLRangeWindow int `json:"hlRangeWindow"` // ma window for kline high/low changes
|
||||
SmootherWindow int `json:"smootherWindow"` // window that controls the smoothness of drift
|
||||
FisherTransformWindow int `json:"fisherTransformWindow"` // fisher transform window to filter drift's negative signals
|
||||
ATRWindow int `json:"atrWindow"` // window for atr indicator
|
||||
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
|
||||
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
|
||||
TrailingCallbackRate []float64 `json:"trailingCallbackRate" modifiable:"true"`
|
||||
TrailingActivationRatio []float64 `json:"trailingActivationRatio" modifiable:"true"`
|
||||
|
||||
buyPrice float64 `persistence:"buy_price"`
|
||||
sellPrice float64 `persistence:"sell_price"`
|
||||
highestPrice float64 `persistence:"highest_price"`
|
||||
lowestPrice float64 `persistence:"lowest_price"`
|
||||
buyPrice float64 `persistence:"buy_price"` // price when a long position is opened
|
||||
sellPrice float64 `persistence:"sell_price"` // price when a short position is opened
|
||||
highestPrice float64 `persistence:"highest_price"` // highestPrice when the position is opened
|
||||
lowestPrice float64 `persistence:"lowest_price"` // lowestPrice when the position is opened
|
||||
|
||||
// This is not related to trade but for statistics graph generation
|
||||
// Will deduct fee in percentage from every trade
|
||||
GraphPNLDeductFee bool `json:"graphPNLDeductFee"`
|
||||
GraphPNLPath string `json:"graphPNLPath"`
|
||||
GraphCumPNLPath string `json:"graphCumPNLPath"`
|
||||
// Whether to generate graph when shutdown
|
||||
GenerateGraph bool `json:"generateGraph"`
|
||||
CanvasPath string `json:"canvasPath"` // backtest related. the path to store the indicator graph
|
||||
GraphPNLPath string `json:"graphPNLPath"` // backtest related. the path to store the pnl % graph per trade graph.
|
||||
GraphCumPNLPath string `json:"graphCumPNLPath"` // backtest related. the path to store the asset changes in graph
|
||||
GenerateGraph bool `json:"generateGraph"` // whether to generate graph when shutdown
|
||||
|
||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||
Session *bbgo.ExchangeSession
|
||||
|
@ -139,7 +140,7 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
|||
|
||||
if !bbgo.IsBackTesting {
|
||||
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
|
||||
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())
|
||||
|
@ -296,6 +297,15 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64) (int, e
|
|||
err := s.GeneralOrderExecutor.CancelNoWait(ctx)
|
||||
// TODO: clean orderPendingCounter on cancel/trade
|
||||
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)
|
||||
}
|
||||
log.Warnf("cancel all %v", err)
|
||||
|
@ -346,7 +356,6 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
|
|||
bestBid := ticker.Buy
|
||||
bestAsk := ticker.Sell
|
||||
|
||||
var pricef float64
|
||||
if !util.TryLock(&s.lock) {
|
||||
return
|
||||
}
|
||||
|
@ -357,37 +366,10 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
|
|||
} else {
|
||||
s.midPrice = bestBid
|
||||
}
|
||||
pricef = s.midPrice.Float64()
|
||||
|
||||
s.lock.Unlock()
|
||||
|
||||
if !util.TryLock(&s.positionLock) {
|
||||
return
|
||||
}
|
||||
// we removed realtime stoploss and trailingStop.
|
||||
|
||||
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) {
|
||||
var ok bool
|
||||
|
@ -585,35 +567,29 @@ func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine) {
|
|||
if s.highestPrice > 0 && highf > s.highestPrice {
|
||||
s.highestPrice = highf
|
||||
}
|
||||
s.positionLock.Unlock()
|
||||
|
||||
numPending := 0
|
||||
var err error
|
||||
if numPending, err = s.smartCancel(ctx, pricef, atr); err != nil {
|
||||
log.WithError(err).Errorf("cannot cancel orders")
|
||||
s.positionLock.Unlock()
|
||||
return
|
||||
}
|
||||
if numPending > 0 {
|
||||
s.positionLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if s.NoTrailingStopLoss || s.TrailingStopLossType == "realtime" {
|
||||
s.positionLock.Unlock()
|
||||
if s.NoTrailingStopLoss {
|
||||
return
|
||||
}
|
||||
|
||||
exitCondition := s.CheckStopLoss() || s.trailingCheck(highf, "short") || s.trailingCheck(lowf, "long")
|
||||
s.positionLock.Unlock()
|
||||
if exitCondition {
|
||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||
var driftPred, atr float64
|
||||
var drift []float64
|
||||
|
||||
s.frameKLine.Set(&kline)
|
||||
|
||||
source := s.GetSource(&kline)
|
||||
|
@ -625,9 +601,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
|||
|
||||
s.atr.PushK(kline)
|
||||
|
||||
driftPred = s.drift.Predict(s.PredictOffset)
|
||||
ddriftPred := s.drift.drift.Predict(s.PredictOffset)
|
||||
atr = s.atr.Last()
|
||||
atr := s.atr.Last()
|
||||
price := s.getLastPrice()
|
||||
pricef := price.Float64()
|
||||
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)
|
||||
highdiff := highf - s.ma.Last()
|
||||
s.stdevHigh.Update(highdiff)
|
||||
drift = s.drift.Array(2)
|
||||
drift := s.drift.Array(2)
|
||||
if len(drift) < 2 || len(drift) < s.PredictOffset {
|
||||
return
|
||||
}
|
||||
|
@ -649,77 +623,78 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
|||
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)
|
||||
|
||||
s.positionLock.Lock()
|
||||
if s.lowestPrice > 0 && lowf < s.lowestPrice {
|
||||
s.lowestPrice = lowf
|
||||
}
|
||||
if s.highestPrice > 0 && highf > s.highestPrice {
|
||||
s.highestPrice = highf
|
||||
}
|
||||
s.positionLock.Unlock()
|
||||
|
||||
if !s.NoRebalance {
|
||||
s.Rebalance(ctx)
|
||||
}
|
||||
|
||||
balances := s.GeneralOrderExecutor.Session().GetAccount().Balances()
|
||||
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",
|
||||
sourcef, pricef, driftPred, ddriftPred, drift[1], ddrift[1], atr, lowf, highf, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice)
|
||||
// Notify will parse args to strings and process separately
|
||||
bbgo.Notify("balances: [Total] %v %s [Base] %s(%v %s) [Quote] %s",
|
||||
s.CalcAssetValue(price),
|
||||
s.Market.QuoteCurrency,
|
||||
balances[s.Market.BaseCurrency].String(),
|
||||
balances[s.Market.BaseCurrency].Total().Mul(price),
|
||||
s.Market.QuoteCurrency,
|
||||
balances[s.Market.QuoteCurrency].String(),
|
||||
)
|
||||
if s.Debug {
|
||||
balances := s.GeneralOrderExecutor.Session().GetAccount().Balances()
|
||||
bbgo.Notify("source: %.4f, price: %.4f, drift[0]: %.4f, ddrift[0]: %.4f, lowf %.4f, highf: %.4f lowest: %.4f highest: %.4f sp %.4f bp %.4f",
|
||||
sourcef, pricef, drift[0], ddrift[0], atr, lowf, highf, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice)
|
||||
// Notify will parse args to strings and process separately
|
||||
bbgo.Notify("balances: [Total] %v %s [Base] %s(%v %s) [Quote] %s",
|
||||
s.CalcAssetValue(price),
|
||||
s.Market.QuoteCurrency,
|
||||
balances[s.Market.BaseCurrency].String(),
|
||||
balances[s.Market.BaseCurrency].Total().Mul(price),
|
||||
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)
|
||||
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 drift[1] > drift[0] {
|
||||
if s.priceLines.Index(1) > s.priceLines.Last() {
|
||||
longCondition = false
|
||||
} else {
|
||||
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 {
|
||||
s.positionLock.Unlock()
|
||||
if err := s.GeneralOrderExecutor.CancelNoWait(ctx); err != nil {
|
||||
if exitCondition || longCondition || shortCondition {
|
||||
var err error
|
||||
var hold int
|
||||
if hold, err = s.smartCancel(ctx, sourcef, atr); err != nil {
|
||||
log.WithError(err).Errorf("cannot cancel orders")
|
||||
}
|
||||
if hold > 0 {
|
||||
return
|
||||
}
|
||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||
if shortCondition || longCondition {
|
||||
s.positionLock.Lock()
|
||||
} else {
|
||||
return
|
||||
} else {
|
||||
if _, err := s.smartCancel(ctx, sourcef, atr); err != nil {
|
||||
log.WithError(err).Errorf("cannot cancel orders")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
if source.Compare(price) > 0 {
|
||||
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())
|
||||
|
||||
s.positionLock.Unlock()
|
||||
opt := s.OpenPositionOptions
|
||||
opt.Long = true
|
||||
opt.LimitOrder = true
|
||||
// force to use market taker
|
||||
if s.counter-s.maxCounterBuyCanceled <= 1 {
|
||||
opt.LimitOrder = false
|
||||
}
|
||||
opt.Price = source
|
||||
opt.Tags = []string{"long"}
|
||||
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
|
||||
|
@ -738,28 +713,20 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
|||
return
|
||||
}
|
||||
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))
|
||||
if source.Compare(price) < 0 {
|
||||
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)
|
||||
|
||||
s.positionLock.Unlock()
|
||||
opt := s.OpenPositionOptions
|
||||
opt.Short = true
|
||||
opt.Price = source
|
||||
opt.LimitOrder = true
|
||||
if s.counter-s.maxCounterSellCanceled <= 1 {
|
||||
opt.LimitOrder = false
|
||||
}
|
||||
opt.Tags = []string{"short"}
|
||||
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
|
||||
if err != nil {
|
||||
|
@ -775,7 +742,6 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
|||
}
|
||||
return
|
||||
}
|
||||
s.positionLock.Unlock()
|
||||
}
|
||||
|
||||
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.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()
|
||||
delete(s.orderPendingCounter, trade.OrderID)
|
||||
|
||||
if s.buyPrice > 0 {
|
||||
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())
|
||||
}
|
||||
s.positionLock.Lock()
|
||||
defer s.positionLock.Unlock()
|
||||
if s.p.IsDust(trade.Price) {
|
||||
s.buyPrice = 0
|
||||
s.sellPrice = 0
|
||||
s.highestPrice = 0
|
||||
s.lowestPrice = 0
|
||||
} else if s.p.IsLong() {
|
||||
s.buyPrice = s.p.ApproximateAverageCost.Float64() // trade.Price.Float64()
|
||||
s.buyPrice = s.p.ApproximateAverageCost.Float64()
|
||||
s.sellPrice = 0
|
||||
s.highestPrice = math.Max(s.buyPrice, s.highestPrice)
|
||||
s.lowestPrice = s.buyPrice
|
||||
} else if s.p.IsShort() {
|
||||
s.sellPrice = s.p.ApproximateAverageCost.Float64() // trade.Price.Float64()
|
||||
s.sellPrice = s.p.ApproximateAverageCost.Float64()
|
||||
s.buyPrice = 0
|
||||
s.highestPrice = s.sellPrice
|
||||
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)
|
||||
}
|
||||
}
|
||||
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{}
|
||||
|
@ -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.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) {
|
||||
canvas := s.DrawIndicators(s.frameKLine.StartTime)
|
||||
var buffer bytes.Buffer
|
||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||
log.WithError(err).Errorf("cannot render indicators in drift")
|
||||
reply.Message(fmt.Sprintf("[error] cannot render indicators in drift: %v", err))
|
||||
return
|
||||
}
|
||||
bbgo.SendPhoto(&buffer)
|
||||
go func() {
|
||||
canvas := s.DrawIndicators(s.frameKLine.StartTime)
|
||||
var buffer bytes.Buffer
|
||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||
log.WithError(err).Errorf("cannot render indicators in drift")
|
||||
reply.Message(fmt.Sprintf("[error] cannot render indicators in drift: %v", err))
|
||||
return
|
||||
}
|
||||
bbgo.SendPhoto(&buffer)
|
||||
}()
|
||||
})
|
||||
|
||||
bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) {
|
||||
canvas := s.DrawPNL(&profit)
|
||||
var buffer bytes.Buffer
|
||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||
log.WithError(err).Errorf("cannot render pnl in drift")
|
||||
reply.Message(fmt.Sprintf("[error] cannot render pnl in drift: %v", err))
|
||||
return
|
||||
}
|
||||
bbgo.SendPhoto(&buffer)
|
||||
go func() {
|
||||
canvas := s.DrawPNL(&profit)
|
||||
var buffer bytes.Buffer
|
||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||
log.WithError(err).Errorf("cannot render pnl in drift")
|
||||
reply.Message(fmt.Sprintf("[error] cannot render pnl in drift: %v", err))
|
||||
return
|
||||
}
|
||||
bbgo.SendPhoto(&buffer)
|
||||
}()
|
||||
})
|
||||
|
||||
bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) {
|
||||
canvas := s.DrawCumPNL(&cumProfit)
|
||||
var buffer bytes.Buffer
|
||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||
log.WithError(err).Errorf("cannot render cumpnl in drift")
|
||||
reply.Message(fmt.Sprintf("[error] canot render cumpnl in drift: %v", err))
|
||||
return
|
||||
}
|
||||
bbgo.SendPhoto(&buffer)
|
||||
go func() {
|
||||
canvas := s.DrawCumPNL(&cumProfit)
|
||||
var buffer bytes.Buffer
|
||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||
log.WithError(err).Errorf("cannot render cumpnl in drift")
|
||||
reply.Message(fmt.Sprintf("[error] canot render cumpnl in drift: %v", err))
|
||||
return
|
||||
}
|
||||
bbgo.SendPhoto(&buffer)
|
||||
}()
|
||||
})
|
||||
|
||||
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) {
|
||||
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 {
|
||||
s.klineHandler(ctx, kline)
|
||||
} 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
|
||||
if !bbgo.IsBackTesting {
|
||||
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 {
|
||||
bbgo.KLinePreloadLimit = int64(((s.Interval.Milliseconds()/s.MinInterval.Milliseconds())*s.WindowSlow/1000 + 1) + 1000)
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
|
||||
|
|
Loading…
Reference in New Issue
Block a user