mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 14:55:16 +00:00
fix: panic on image drawing, reduce fee by smoothing the drift curve
This commit is contained in:
parent
553a55811c
commit
2ceb24ad09
|
@ -21,15 +21,14 @@ exchangeStrategies:
|
||||||
# kline interval for indicators
|
# kline interval for indicators
|
||||||
interval: 15m
|
interval: 15m
|
||||||
window: 2
|
window: 2
|
||||||
stoploss: 2%
|
stoploss: 0.3%
|
||||||
source: close
|
source: close
|
||||||
predictOffset: 3
|
predictOffset: 2
|
||||||
# position avg +- takeProfitFactor * atr as take profit price
|
# position avg +- takeProfitFactor * atr as take profit price
|
||||||
takeProfitFactor: 1
|
takeProfitFactor: 1.4
|
||||||
noStopPrice: true
|
noTrailingStopLoss: true
|
||||||
noTrailingStopLoss: false
|
|
||||||
# stddev on high/low-source
|
# stddev on high/low-source
|
||||||
hlVarianceMultiplier: 0.34
|
hlVarianceMultiplier: 0.22
|
||||||
|
|
||||||
generateGraph: true
|
generateGraph: true
|
||||||
graphPNLDeductFee: false
|
graphPNLDeductFee: false
|
||||||
|
|
91
config/driftBTC.yaml
Normal file
91
config/driftBTC.yaml
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
---
|
||||||
|
persistence:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
|
db: 0
|
||||||
|
|
||||||
|
sessions:
|
||||||
|
binance:
|
||||||
|
exchange: binance
|
||||||
|
futures: false
|
||||||
|
envVarPrefix: binance
|
||||||
|
heikinAshi: false
|
||||||
|
|
||||||
|
exchangeStrategies:
|
||||||
|
|
||||||
|
- on: binance
|
||||||
|
drift:
|
||||||
|
canvasPath: "./output.png"
|
||||||
|
symbol: BTCBUSD
|
||||||
|
# kline interval for indicators
|
||||||
|
interval: 15m
|
||||||
|
window: 2
|
||||||
|
stoploss: 0.3%
|
||||||
|
source: close
|
||||||
|
predictOffset: 2
|
||||||
|
# position avg +- takeProfitFactor * atr as take profit price
|
||||||
|
takeProfitFactor: 1.2
|
||||||
|
noTrailingStopLoss: true
|
||||||
|
# stddev on high/low-source
|
||||||
|
hlVarianceMultiplier: 0.27
|
||||||
|
|
||||||
|
generateGraph: true
|
||||||
|
graphPNLDeductFee: true
|
||||||
|
graphPNLPath: "./pnl.png"
|
||||||
|
graphCumPNLPath: "./cumpnl.png"
|
||||||
|
exits:
|
||||||
|
#- roiStopLoss:
|
||||||
|
# percentage: 0.8%
|
||||||
|
#- roiTakeProfit:
|
||||||
|
# percentage: 3%
|
||||||
|
#- protectiveStopLoss:
|
||||||
|
# activationRatio: 0.5%
|
||||||
|
# stopLossRatio: 0.1%
|
||||||
|
# placeStopOrder: false
|
||||||
|
- trailingStop:
|
||||||
|
callbackRate: 1%
|
||||||
|
# activationRatio is relative to the average cost,
|
||||||
|
# when side is buy, 1% means lower 1% than the average cost.
|
||||||
|
# when side is sell, 1% means higher 1% than the average cost.
|
||||||
|
activationRatio: 3%
|
||||||
|
# minProfit uses the position ROI to calculate the profit ratio
|
||||||
|
minProfit: 1%
|
||||||
|
interval: 1m
|
||||||
|
side: buy
|
||||||
|
closePosition: 100%
|
||||||
|
#- 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:
|
||||||
|
- BTCBUSD
|
||||||
|
|
||||||
|
backtest:
|
||||||
|
startTime: "2022-01-01"
|
||||||
|
endTime: "2022-06-18"
|
||||||
|
symbols:
|
||||||
|
- BTCBUSD
|
||||||
|
sessions: [binance]
|
||||||
|
accounts:
|
||||||
|
binance:
|
||||||
|
makerFeeRate: 0.000
|
||||||
|
takerFeeRate: 0.00075
|
||||||
|
balances:
|
||||||
|
BTC: 10
|
||||||
|
BUSD: 5000.0
|
|
@ -130,6 +130,28 @@ func New(key, secret string) *Exchange {
|
||||||
log.WithError(err).Error("can not set server time")
|
log.WithError(err).Error("can not set server time")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(time.Hour)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
_, err = client.NewSetServerTimeService().Do(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("can not set server time")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = futuresClient.NewSetServerTimeService().Do(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("can not set server time")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client2.SetTimeOffsetFromServer(context.Background()); err != nil {
|
||||||
|
log.WithError(err).Error("can not set server time")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
return ex
|
return ex
|
||||||
|
|
|
@ -21,7 +21,6 @@ Bollinger Bands Technical indicator guide:
|
||||||
|
|
||||||
//go:generate callbackgen -type BOLL
|
//go:generate callbackgen -type BOLL
|
||||||
type BOLL struct {
|
type BOLL struct {
|
||||||
types.SeriesBase
|
|
||||||
types.IntervalWindow
|
types.IntervalWindow
|
||||||
|
|
||||||
// K is the multiplier of Std, generally it's 2
|
// K is the multiplier of Std, generally it's 2
|
||||||
|
@ -74,7 +73,6 @@ func (inc *BOLL) LastDownBand() float64 {
|
||||||
|
|
||||||
func (inc *BOLL) Update(value float64) {
|
func (inc *BOLL) Update(value float64) {
|
||||||
if inc.SMA == nil {
|
if inc.SMA == nil {
|
||||||
inc.SeriesBase.Series = inc
|
|
||||||
inc.SMA = &SMA{IntervalWindow: inc.IntervalWindow}
|
inc.SMA = &SMA{IntervalWindow: inc.IntervalWindow}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,27 @@ func (inc *Drift) Update(value float64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assume that MA is SMA
|
||||||
|
func (inc *Drift) ZeroPoint() float64 {
|
||||||
|
window := float64(inc.Window)
|
||||||
|
stdev := types.Stdev(inc.chng, inc.Window)
|
||||||
|
chng := inc.chng.Index(inc.Window - 1)
|
||||||
|
/*b := -2 * inc.MA.Last() - 2
|
||||||
|
c := window * stdev * stdev - chng * chng + 2 * chng * (inc.MA.Last() + 1) - 2 * inc.MA.Last() * window
|
||||||
|
|
||||||
|
root := math.Sqrt(b*b - 4*c)
|
||||||
|
K1 := (-b + root)/2
|
||||||
|
K2 := (-b - root)/2
|
||||||
|
N1 := math.Exp(K1) * inc.LastValue
|
||||||
|
N2 := math.Exp(K2) * inc.LastValue
|
||||||
|
if math.Abs(inc.LastValue-N1) < math.Abs(inc.LastValue-N2) {
|
||||||
|
return N1
|
||||||
|
} else {
|
||||||
|
return N2
|
||||||
|
}*/
|
||||||
|
return inc.LastValue * math.Exp(window*(0.5*stdev*stdev)+chng-inc.MA.Last()*window)
|
||||||
|
}
|
||||||
|
|
||||||
func (inc *Drift) Clone() (out *Drift) {
|
func (inc *Drift) Clone() (out *Drift) {
|
||||||
out = &Drift{
|
out = &Drift{
|
||||||
IntervalWindow: inc.IntervalWindow,
|
IntervalWindow: inc.IntervalWindow,
|
||||||
|
|
58
pkg/indicator/fisher.go
Normal file
58
pkg/indicator/fisher.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package indicator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate callbackgen -type FisherTransform
|
||||||
|
type FisherTransform struct {
|
||||||
|
types.SeriesBase
|
||||||
|
types.IntervalWindow
|
||||||
|
prices *types.Queue
|
||||||
|
Values types.Float64Slice
|
||||||
|
|
||||||
|
UpdateCallbacks []func(value float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *FisherTransform) Update(value float64) {
|
||||||
|
if inc.prices == nil {
|
||||||
|
inc.prices = types.NewQueue(inc.Window)
|
||||||
|
inc.SeriesBase.Series = inc
|
||||||
|
}
|
||||||
|
inc.prices.Update(value)
|
||||||
|
highest := inc.prices.Highest(inc.Window)
|
||||||
|
lowest := inc.prices.Lowest(inc.Window)
|
||||||
|
x := 2*((value-lowest)/(highest-lowest)) - 1
|
||||||
|
if x == 1 {
|
||||||
|
x = 0.9999
|
||||||
|
} else if x == -1 {
|
||||||
|
x = -0.9999
|
||||||
|
}
|
||||||
|
inc.Values.Update(0.5 * math.Log((1+x)/(1-x)))
|
||||||
|
if len(inc.Values) > MaxNumOfEWMA {
|
||||||
|
inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *FisherTransform) Last() float64 {
|
||||||
|
if inc.Values == nil {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return inc.Values.Last()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *FisherTransform) Index(i int) float64 {
|
||||||
|
if inc.Values == nil {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return inc.Values.Index(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *FisherTransform) Length() int {
|
||||||
|
if inc.Values == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return inc.Values.Length()
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ type Strategy struct {
|
||||||
ma types.UpdatableSeriesExtend
|
ma types.UpdatableSeriesExtend
|
||||||
stdevHigh *indicator.StdDev
|
stdevHigh *indicator.StdDev
|
||||||
stdevLow *indicator.StdDev
|
stdevLow *indicator.StdDev
|
||||||
drift *indicator.Drift
|
drift *DriftMA
|
||||||
atr *indicator.ATR
|
atr *indicator.ATR
|
||||||
midPrice fixedpoint.Value
|
midPrice fixedpoint.Value
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
@ -62,11 +62,12 @@ type Strategy struct {
|
||||||
CanvasPath string `json:"canvasPath"`
|
CanvasPath string `json:"canvasPath"`
|
||||||
PredictOffset int `json:"predictOffset"`
|
PredictOffset int `json:"predictOffset"`
|
||||||
HighLowVarianceMultiplier float64 `json:"hlVarianceMultiplier"`
|
HighLowVarianceMultiplier float64 `json:"hlVarianceMultiplier"`
|
||||||
NoStopPrice bool `json:"noStopPrice"`
|
|
||||||
NoTrailingStopLoss bool `json:"noTrailingStopLoss"`
|
NoTrailingStopLoss bool `json:"noTrailingStopLoss"`
|
||||||
|
|
||||||
buyPrice float64
|
buyPrice float64
|
||||||
sellPrice float64
|
sellPrice float64
|
||||||
|
highestPrice float64
|
||||||
|
lowestPrice float64
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -76,8 +77,6 @@ type Strategy struct {
|
||||||
// Whether to generate graph when shutdown
|
// Whether to generate graph when shutdown
|
||||||
GenerateGraph bool `json:"generateGraph"`
|
GenerateGraph bool `json:"generateGraph"`
|
||||||
|
|
||||||
StopOrders map[uint64]*types.SubmitOrder
|
|
||||||
|
|
||||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||||
Session *bbgo.ExchangeSession
|
Session *bbgo.ExchangeSession
|
||||||
*bbgo.GeneralOrderExecutor
|
*bbgo.GeneralOrderExecutor
|
||||||
|
@ -101,7 +100,6 @@ func (s *Strategy) Print(o *os.File) {
|
||||||
hiyellow(f, "symbol: %s\n", s.Symbol)
|
hiyellow(f, "symbol: %s\n", s.Symbol)
|
||||||
hiyellow(f, "interval: %s\n", s.Interval)
|
hiyellow(f, "interval: %s\n", s.Interval)
|
||||||
hiyellow(f, "window: %d\n", s.Window)
|
hiyellow(f, "window: %d\n", s.Window)
|
||||||
hiyellow(f, "noStopPrice: %v\n", s.NoStopPrice)
|
|
||||||
hiyellow(f, "noTrailingStopLoss: %v\n", s.NoTrailingStopLoss)
|
hiyellow(f, "noTrailingStopLoss: %v\n", s.NoTrailingStopLoss)
|
||||||
hiyellow(f, "hlVarianceMutiplier: %f\n", s.HighLowVarianceMultiplier)
|
hiyellow(f, "hlVarianceMutiplier: %f\n", s.HighLowVarianceMultiplier)
|
||||||
hiyellow(f, "\n")
|
hiyellow(f, "\n")
|
||||||
|
@ -112,7 +110,7 @@ func (s *Strategy) ID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) InstanceID() string {
|
func (s *Strategy) InstanceID() string {
|
||||||
return fmt.Sprintf("%s:%s", ID, s.Symbol)
|
return fmt.Sprintf("%s-%s", ID, s.Symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
|
@ -138,15 +136,6 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu
|
||||||
if order == nil {
|
if order == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if percentage.Compare(fixedpoint.One) == 0 {
|
|
||||||
// Cleanup pending StopOrders
|
|
||||||
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
|
||||||
} else {
|
|
||||||
// Should only have one stop order
|
|
||||||
for _, o := range s.StopOrders {
|
|
||||||
o.Quantity = o.Quantity.Mul(fixedpoint.One.Sub(percentage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
order.Tag = "close"
|
order.Tag = "close"
|
||||||
order.TimeInForce = ""
|
order.TimeInForce = ""
|
||||||
balances := s.Session.GetAccount().Balances()
|
balances := s.Session.GetAccount().Balances()
|
||||||
|
@ -205,54 +194,52 @@ func (s *Strategy) SourceFuncGenerator() SourceFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) BindStopLoss(ctx context.Context) {
|
type DriftMA struct {
|
||||||
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
types.SeriesBase
|
||||||
s.Session.UserDataStream.OnOrderUpdate(func(order types.Order) {
|
ma1 types.UpdatableSeries
|
||||||
if len(s.StopOrders) == 0 {
|
drift *indicator.Drift
|
||||||
return
|
ma2 types.UpdatableSeries
|
||||||
}
|
}
|
||||||
if order.Symbol != s.Symbol {
|
|
||||||
return
|
func (s *DriftMA) Update(value float64) {
|
||||||
|
s.ma1.Update(value)
|
||||||
|
s.drift.Update(s.ma1.Last())
|
||||||
|
s.ma2.Update(s.drift.Last())
|
||||||
}
|
}
|
||||||
if order.Status == types.OrderStatusCanceled {
|
|
||||||
delete(s.StopOrders, order.OrderID)
|
func (s *DriftMA) Last() float64 {
|
||||||
return
|
return s.ma2.Last()
|
||||||
}
|
}
|
||||||
if order.Status != types.OrderStatusFilled {
|
|
||||||
return
|
func (s *DriftMA) Index(i int) float64 {
|
||||||
|
return s.ma2.Index(i)
|
||||||
}
|
}
|
||||||
if o, ok := s.StopOrders[order.OrderID]; ok {
|
|
||||||
delete(s.StopOrders, order.OrderID)
|
func (s *DriftMA) Length() int {
|
||||||
if o.Side == types.SideTypeBuy {
|
return s.ma2.Length()
|
||||||
quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency)
|
|
||||||
if !ok {
|
|
||||||
log.Errorf("unable to get quoteCurrency")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
o.Quantity = quoteBalance.Available.Div(o.Price)
|
|
||||||
} else {
|
func (s *DriftMA) ZeroPoint() float64 {
|
||||||
baseBalance, ok := s.Session.GetAccount().Balance(s.Market.BaseCurrency)
|
return s.drift.ZeroPoint()
|
||||||
if !ok {
|
|
||||||
log.Errorf("unable to get baseCurrency")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
o.Quantity = baseBalance.Available
|
|
||||||
}
|
|
||||||
if _, err := s.GeneralOrderExecutor.SubmitOrders(ctx, *o); err != nil {
|
|
||||||
log.WithError(err).Errorf("cannot send stop order: %v", order)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) InitIndicators() error {
|
func (s *Strategy) InitIndicators() error {
|
||||||
s.ma = &indicator.EWMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 5}}
|
s.ma = &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 5}}
|
||||||
s.stdevHigh = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 6}}
|
s.stdevHigh = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 6}}
|
||||||
s.stdevLow = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 6}}
|
s.stdevLow = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 6}}
|
||||||
s.drift = &indicator.Drift{
|
s.drift = &DriftMA{
|
||||||
|
drift: &indicator.Drift{
|
||||||
MA: &indicator.SMA{IntervalWindow: s.IntervalWindow},
|
MA: &indicator.SMA{IntervalWindow: s.IntervalWindow},
|
||||||
IntervalWindow: s.IntervalWindow,
|
IntervalWindow: s.IntervalWindow,
|
||||||
|
},
|
||||||
|
ma1: &indicator.EWMA{
|
||||||
|
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 2},
|
||||||
|
},
|
||||||
|
ma2: &indicator.FisherTransform{
|
||||||
|
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 9},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
s.drift.SeriesBase.Series = s.drift
|
||||||
s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 14}}
|
s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 14}}
|
||||||
store, _ := s.Session.MarketDataStore(s.Symbol)
|
store, _ := s.Session.MarketDataStore(s.Symbol)
|
||||||
klines, ok := store.KLinesOfInterval(s.Interval)
|
klines, ok := store.KLinesOfInterval(s.Interval)
|
||||||
|
@ -299,10 +286,15 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) {
|
||||||
}
|
}
|
||||||
price = s.midPrice
|
price = s.midPrice
|
||||||
pricef = s.midPrice.Float64()
|
pricef = s.midPrice.Float64()
|
||||||
s.lock.Unlock()
|
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if s.highestPrice > 0 && s.highestPrice < pricef {
|
||||||
|
s.highestPrice = pricef
|
||||||
|
}
|
||||||
|
if s.lowestPrice > 0 && s.lowestPrice > pricef {
|
||||||
|
s.lowestPrice = pricef
|
||||||
|
}
|
||||||
|
|
||||||
// for trailing stoploss during the realtime
|
// for trailing stoploss during the realtime
|
||||||
if s.NoTrailingStopLoss {
|
if s.NoTrailingStopLoss {
|
||||||
|
@ -311,9 +303,11 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) {
|
||||||
atr = s.atr.Last()
|
atr = s.atr.Last()
|
||||||
avg = s.buyPrice + s.sellPrice
|
avg = s.buyPrice + s.sellPrice
|
||||||
stoploss = s.StopLoss.Float64()
|
stoploss = s.StopLoss.Float64()
|
||||||
exitShortCondition := (avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef || avg-atr*s.TakeProfitFactor >= pricef) &&
|
exitShortCondition := (avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef || avg-atr*s.TakeProfitFactor >= pricef ||
|
||||||
|
((pricef-s.lowestPrice)/pricef > stoploss && (s.sellPrice-s.lowestPrice)/s.sellPrice > 0.01)) &&
|
||||||
(s.Position.IsShort() && !s.Position.IsDust(price))
|
(s.Position.IsShort() && !s.Position.IsDust(price))
|
||||||
exitLongCondition := (avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef || avg+atr*s.TakeProfitFactor <= pricef) &&
|
exitLongCondition := (avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef || avg+atr*s.TakeProfitFactor <= pricef ||
|
||||||
|
((s.highestPrice-pricef)/pricef > stoploss && (s.highestPrice-s.buyPrice)/s.buyPrice > 0.01)) &&
|
||||||
(!s.Position.IsLong() && !s.Position.IsDust(price))
|
(!s.Position.IsLong() && !s.Position.IsDust(price))
|
||||||
if exitShortCondition || exitLongCondition {
|
if exitShortCondition || exitLongCondition {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
|
@ -322,6 +316,7 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) {
|
||||||
}
|
}
|
||||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||||
}
|
}
|
||||||
|
s.lock.Unlock()
|
||||||
|
|
||||||
})
|
})
|
||||||
s.getLastPrice = func() (lastPrice fixedpoint.Value) {
|
s.getLastPrice = func() (lastPrice fixedpoint.Value) {
|
||||||
|
@ -343,19 +338,25 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit types.Series, cumProfit types.Series) {
|
func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit types.Series, cumProfit types.Series, zeroPoints types.Series) {
|
||||||
canvas := types.NewCanvas(s.InstanceID(), s.Interval)
|
canvas := types.NewCanvas(s.InstanceID(), s.Interval)
|
||||||
Length := priceLine.Length()
|
Length := priceLine.Length()
|
||||||
if Length > 100 {
|
if Length > 300 {
|
||||||
Length = 100
|
Length = 300
|
||||||
}
|
}
|
||||||
mean := priceLine.Mean(Length)
|
mean := priceLine.Mean(Length)
|
||||||
highestPrice := priceLine.Minus(mean).Abs().Highest(Length)
|
highestPrice := priceLine.Minus(mean).Abs().Highest(Length)
|
||||||
highestDrift := s.drift.Abs().Highest(Length)
|
highestDrift := s.drift.Abs().Highest(Length)
|
||||||
ratio := highestDrift / highestPrice
|
hi := s.drift.drift.Abs().Highest(Length)
|
||||||
canvas.Plot("drift", s.drift, time, Length)
|
ratio := highestPrice / highestDrift
|
||||||
canvas.Plot("zero", types.NumberSeries(0), time, Length)
|
canvas.Plot("upband", s.ma.Add(s.stdevHigh), time, Length)
|
||||||
canvas.Plot("price", priceLine.Minus(mean).Mul(ratio), time, Length)
|
canvas.Plot("ma", s.ma, time, Length)
|
||||||
|
canvas.Plot("downband", s.ma.Minus(s.stdevLow), time, Length)
|
||||||
|
canvas.Plot("drift", s.drift.Mul(ratio).Add(mean), time, Length)
|
||||||
|
canvas.Plot("driftOrig", s.drift.drift.Mul(highestPrice/hi).Add(mean), time, Length)
|
||||||
|
canvas.Plot("zero", types.NumberSeries(mean), time, Length)
|
||||||
|
canvas.Plot("price", priceLine, time, Length)
|
||||||
|
canvas.Plot("zeroPoint", zeroPoints, time, Length)
|
||||||
f, err := os.Create(s.CanvasPath)
|
f, err := os.Create(s.CanvasPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Errorf("cannot create on %s", s.CanvasPath)
|
log.WithError(err).Errorf("cannot create on %s", s.CanvasPath)
|
||||||
|
@ -532,26 +533,13 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
Volume = fixedpoint.Zero
|
Volume = fixedpoint.Zero
|
||||||
}
|
}
|
||||||
Volume = Volume.Add(trade.Quantity)
|
Volume = Volume.Add(trade.Quantity)
|
||||||
} else if tag == "sl" {
|
|
||||||
// TODO: not properly handled for single order, multiple trades
|
|
||||||
if !buyPrice.IsZero() {
|
|
||||||
profit.Update(modify(trade.Price.Div(buyPrice)).Float64())
|
|
||||||
cumProfit.Update(cumProfit.Last() * profit.Last())
|
|
||||||
buyPrice = fixedpoint.Zero
|
|
||||||
} else if !sellPrice.IsZero() {
|
|
||||||
profit.Update(modify(sellPrice.Div(trade.Price)).Float64())
|
|
||||||
cumProfit.Update(cumProfit.Last() * profit.Last())
|
|
||||||
sellPrice = fixedpoint.Zero
|
|
||||||
} else {
|
|
||||||
panic("no position to sl")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
s.buyPrice = buyPrice.Float64()
|
s.buyPrice = buyPrice.Float64()
|
||||||
|
s.highestPrice = s.buyPrice
|
||||||
s.sellPrice = sellPrice.Float64()
|
s.sellPrice = sellPrice.Float64()
|
||||||
|
s.lowestPrice = s.sellPrice
|
||||||
})
|
})
|
||||||
|
|
||||||
s.BindStopLoss(ctx)
|
|
||||||
|
|
||||||
if err := s.InitIndicators(); err != nil {
|
if err := s.InitIndicators(); err != nil {
|
||||||
log.WithError(err).Errorf("InitIndicator failed")
|
log.WithError(err).Errorf("InitIndicator failed")
|
||||||
return nil
|
return nil
|
||||||
|
@ -559,7 +547,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.InitTickerFunctions(ctx)
|
s.InitTickerFunctions(ctx)
|
||||||
|
|
||||||
dynamicKLine := &types.KLine{}
|
dynamicKLine := &types.KLine{}
|
||||||
priceLine := types.NewQueue(100)
|
priceLine := types.NewQueue(300)
|
||||||
|
zeroPoints := types.NewQueue(300)
|
||||||
stoploss := s.StopLoss.Float64()
|
stoploss := s.StopLoss.Float64()
|
||||||
|
|
||||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||||
|
@ -585,11 +574,19 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
pricef := price.Float64()
|
pricef := price.Float64()
|
||||||
lowf := math.Min(kline.Low.Float64(), pricef)
|
lowf := math.Min(kline.Low.Float64(), pricef)
|
||||||
highf := math.Max(kline.High.Float64(), pricef)
|
highf := math.Max(kline.High.Float64(), pricef)
|
||||||
|
if s.lowestPrice > 0 && lowf < s.lowestPrice {
|
||||||
|
s.lowestPrice = lowf
|
||||||
|
}
|
||||||
|
if s.highestPrice > 0 && highf > s.highestPrice {
|
||||||
|
s.highestPrice = highf
|
||||||
|
}
|
||||||
avg := s.buyPrice + s.sellPrice
|
avg := s.buyPrice + s.sellPrice
|
||||||
|
|
||||||
exitShortCondition := (avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) &&
|
exitShortCondition := (avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf ||
|
||||||
|
((highf-s.lowestPrice)/pricef > stoploss && (s.sellPrice-s.lowestPrice)/s.sellPrice > 0.01)) &&
|
||||||
(s.Position.IsShort() && !s.Position.IsDust(price))
|
(s.Position.IsShort() && !s.Position.IsDust(price))
|
||||||
exitLongCondition := (avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf) &&
|
exitLongCondition := (avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf ||
|
||||||
|
((s.highestPrice-pricef)/pricef > stoploss && (s.highestPrice-s.buyPrice)/s.buyPrice > 0.01)) &&
|
||||||
(s.Position.IsLong() && !s.Position.IsDust(price))
|
(s.Position.IsLong() && !s.Position.IsDust(price))
|
||||||
if exitShortCondition || exitLongCondition {
|
if exitShortCondition || exitLongCondition {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
|
@ -607,8 +604,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
priceLine.Update(sourcef)
|
priceLine.Update(sourcef)
|
||||||
s.ma.Update(sourcef)
|
s.ma.Update(sourcef)
|
||||||
s.drift.Update(sourcef)
|
s.drift.Update(sourcef)
|
||||||
|
zeroPoint := s.drift.ZeroPoint()
|
||||||
|
zeroPoints.Update(zeroPoint)
|
||||||
s.atr.PushK(kline)
|
s.atr.PushK(kline)
|
||||||
drift = s.drift.Array(2)
|
drift = s.drift.Array(2)
|
||||||
|
ddrift := s.drift.drift.Array(2)
|
||||||
driftPred = s.drift.Predict(s.PredictOffset)
|
driftPred = s.drift.Predict(s.PredictOffset)
|
||||||
atr = s.atr.Last()
|
atr = s.atr.Last()
|
||||||
price := s.getLastPrice()
|
price := s.getLastPrice()
|
||||||
|
@ -623,18 +623,22 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
|
|
||||||
if !s.IsBackTesting() {
|
if !s.IsBackTesting() {
|
||||||
balances := s.Session.GetAccount().Balances()
|
balances := s.Session.GetAccount().Balances()
|
||||||
bbgo.Notify("source: %.4f, price: %.4f, driftPred: %.4f, drift: %.4f, drift[1]: %.4f, atr: %.4f, avg: %.4f",
|
bbgo.Notify("zeroPoint: %.4f, source: %.4f, price: %.4f, driftPred: %.4f, drift: %.4f, drift[1]: %.4f, atr: %.4f, avg: %.4f",
|
||||||
sourcef, pricef, driftPred, drift[0], drift[1], atr, avg)
|
zeroPoint, sourcef, pricef, driftPred, drift[0], drift[1], atr, avg)
|
||||||
// Notify will parse args to strings and process separately
|
// Notify will parse args to strings and process separately
|
||||||
bbgo.Notify("balances: [Base] %s [Quote] %s", balances[s.Market.BaseCurrency].String(), balances[s.Market.QuoteCurrency].String())
|
bbgo.Notify("balances: [Base] %s [Quote] %s", balances[s.Market.BaseCurrency].String(), balances[s.Market.QuoteCurrency].String())
|
||||||
}
|
}
|
||||||
|
|
||||||
shortCondition := (driftPred <= 0 && drift[0] <= 0)
|
//shortCondition := (sourcef <= zeroPoint && driftPred <= drift[0] && drift[0] <= 0 && drift[1] > 0 && drift[2] > drift[1])
|
||||||
longCondition := (driftPred >= 0 && drift[0] >= 0)
|
//longCondition := (sourcef >= zeroPoint && driftPred >= drift[0] && drift[0] >= 0 && drift[1] < 0 && drift[2] < drift[1])
|
||||||
exitShortCondition := ((drift[1] < 0 && drift[0] >= 0) || avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) &&
|
//bothUp := ddrift[1] < ddrift[0] && drift[1] < drift[0]
|
||||||
(s.Position.IsShort() && !s.Position.IsDust(fixedpoint.Max(price, source))) && !longCondition
|
//bothDown := ddrift[1] > ddrift[0] && drift[1] > drift[0]
|
||||||
exitLongCondition := ((drift[1] > 0 && drift[0] < 0) || avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf) &&
|
shortCondition := (ddrift[0] <= 0 || drift[0] <= 0) && driftPred < 0.
|
||||||
(s.Position.IsLong() && !s.Position.IsDust(fixedpoint.Min(price, source))) && !shortCondition
|
longCondition := (ddrift[0] >= 0 || drift[0] >= 0) && driftPred > 0
|
||||||
|
exitShortCondition := (avg+atr <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) &&
|
||||||
|
(s.Position.IsShort() && !s.Position.IsDust(fixedpoint.Max(price, source))) && !longCondition && !shortCondition
|
||||||
|
exitLongCondition := (avg-atr >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf) &&
|
||||||
|
(s.Position.IsLong() && !s.Position.IsDust(fixedpoint.Min(price, source))) && !shortCondition && !longCondition
|
||||||
|
|
||||||
if exitShortCondition || exitLongCondition {
|
if exitShortCondition || exitLongCondition {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
|
@ -642,6 +646,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if shortCondition {
|
if shortCondition {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
|
@ -663,18 +668,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Cleanup pending StopOrders
|
// Cleanup pending StopOrders
|
||||||
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
|
||||||
quantity := baseBalance.Available
|
quantity := baseBalance.Available
|
||||||
stopPrice := fixedpoint.NewFromFloat(math.Min(sourcef+atr/2, sourcef*(1.+stoploss)))
|
|
||||||
stopOrder := types.SubmitOrder{
|
|
||||||
Symbol: s.Symbol,
|
|
||||||
Side: types.SideTypeBuy,
|
|
||||||
Type: types.OrderTypeStopLimit,
|
|
||||||
StopPrice: stopPrice,
|
|
||||||
Price: stopPrice,
|
|
||||||
Quantity: quantity,
|
|
||||||
Tag: "sl",
|
|
||||||
}
|
|
||||||
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||||
Symbol: s.Symbol,
|
Symbol: s.Symbol,
|
||||||
Side: types.SideTypeSell,
|
Side: types.SideTypeSell,
|
||||||
|
@ -688,16 +682,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
orderTagHistory[createdOrders[0].OrderID] = "short"
|
orderTagHistory[createdOrders[0].OrderID] = "short"
|
||||||
if s.NoStopPrice {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if createdOrders[0].Status == types.OrderStatusFilled {
|
|
||||||
if o, err := s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder); err == nil {
|
|
||||||
orderTagHistory[o[0].OrderID] = "sl"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.StopOrders[createdOrders[0].OrderID] = &stopOrder
|
|
||||||
}
|
}
|
||||||
if longCondition {
|
if longCondition {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
|
@ -709,6 +693,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
source = price
|
source = price
|
||||||
}
|
}
|
||||||
sourcef = source.Float64()
|
sourcef = source.Float64()
|
||||||
|
|
||||||
quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency)
|
quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("unable to get quoteCurrency")
|
log.Errorf("unable to get quoteCurrency")
|
||||||
|
@ -718,20 +703,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
quoteBalance.Available.Div(source), source) {
|
quoteBalance.Available.Div(source), source) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Cleanup pending StopOrders
|
|
||||||
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
|
||||||
quantity := quoteBalance.Available.Div(source)
|
quantity := quoteBalance.Available.Div(source)
|
||||||
stopPrice := fixedpoint.NewFromFloat(math.Max(sourcef-atr/2, sourcef*(1.-stoploss)))
|
|
||||||
stopOrder := types.SubmitOrder{
|
|
||||||
Symbol: s.Symbol,
|
|
||||||
Side: types.SideTypeSell,
|
|
||||||
Type: types.OrderTypeStopLimit,
|
|
||||||
TimeInForce: types.TimeInForceGTC,
|
|
||||||
StopPrice: stopPrice,
|
|
||||||
Price: stopPrice,
|
|
||||||
Quantity: quantity,
|
|
||||||
Tag: "sl",
|
|
||||||
}
|
|
||||||
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||||
Symbol: s.Symbol,
|
Symbol: s.Symbol,
|
||||||
Side: types.SideTypeBuy,
|
Side: types.SideTypeBuy,
|
||||||
|
@ -745,16 +717,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
orderTagHistory[createdOrders[0].OrderID] = "long"
|
orderTagHistory[createdOrders[0].OrderID] = "long"
|
||||||
if s.NoStopPrice {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if createdOrders[0].Status == types.OrderStatusFilled {
|
|
||||||
if o, err := s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder); err == nil {
|
|
||||||
orderTagHistory[o[0].OrderID] = "sl"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.StopOrders[createdOrders[0].OrderID] = &stopOrder
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -765,7 +727,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
defer fmt.Fprintln(os.Stdout, s.TradeStats.BriefString())
|
defer fmt.Fprintln(os.Stdout, s.TradeStats.BriefString())
|
||||||
|
|
||||||
if s.GenerateGraph {
|
if s.GenerateGraph {
|
||||||
s.Draw(dynamicKLine.StartTime, priceLine, &profit, &cumProfit)
|
s.Draw(dynamicKLine.StartTime, priceLine, &profit, &cumProfit, zeroPoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Done()
|
wg.Done()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user