fix: add series.filter, fix fixedpoint.Four, fix modifiable for embedded fields, change drift to use openPosition, modify openPosition behavior

This commit is contained in:
zenix 2022-09-22 13:01:26 +09:00
parent da6161ddda
commit fd875c7060
12 changed files with 276 additions and 142 deletions

View File

@ -25,35 +25,36 @@ exchangeStrategies:
drift: drift:
canvasPath: "./output.png" canvasPath: "./output.png"
symbol: ETHBUSD symbol: ETHBUSD
limitOrder: false
# kline interval for indicators # kline interval for indicators
interval: 15m interval: 2m
window: 2 window: 6
stoploss: 4.3% stoploss: 0.23%
source: close source: ohlc4
predictOffset: 2 predictOffset: 2
noTrailingStopLoss: true noTrailingStopLoss: false
trailingStopLossType: kline trailingStopLossType: kline
# stddev on high/low-source # stddev on high/low-source
hlVarianceMultiplier: 0.1 hlVarianceMultiplier: 0.03
hlRangeWindow: 5 hlRangeWindow: 4
window1m: 49
smootherWindow1m: 80
fisherTransformWindow1m: 74
smootherWindow: 3 smootherWindow: 3
fisherTransformWindow: 160 fisherTransformWindow: 117
window1m: 42
smootherWindow1m: 118
fisherTransformWindow1m: 319
atrWindow: 14 atrWindow: 14
# orders not been traded will be canceled after `pendingMinutes` minutes # orders not been traded will be canceled after `pendingMinutes` minutes
pendingMinutes: 10 pendingMinutes: 3
noRebalance: true noRebalance: true
trendWindow: 12 trendWindow: 12
rebalanceFilter: 1.5 rebalanceFilter: 2
trailingActivationRatio: [0.003] trailingActivationRatio: [0.0015, 0.002, 0.004, 0.01]
trailingCallbackRate: [0.0006] trailingCallbackRate: [0.0001, 0.00012, 0.001, 0.002]
driftFilterPos: 1.2 #driftFilterPos: 0.4
driftFilterNeg: -1.2 #driftFilterNeg: -0.42
ddriftFilterPos: 0.4 #ddriftFilterPos: 0
ddriftFilterNeg: -0.4 #ddriftFilterNeg: 0
generateGraph: true generateGraph: true
graphPNLDeductFee: true graphPNLDeductFee: true
@ -91,15 +92,15 @@ sync:
- ETHBUSD - ETHBUSD
backtest: backtest:
startTime: "2022-01-01" startTime: "2022-09-01"
endTime: "2022-08-30" endTime: "2022-09-30"
symbols: symbols:
- ETHBUSD - ETHBUSD
sessions: [binance] sessions: [binance]
accounts: accounts:
binance: binance:
makerFeeRate: 0.0000 makerFeeRate: 0.0000
#takerFeeRate: 0.00001 takerFeeRate: 0.0000
balances: balances:
ETH: 10 ETH: 0
BUSD: 5000.0 BUSD: 1000.0

View File

@ -8,7 +8,10 @@ persistence:
sessions: sessions:
binance: binance:
exchange: binance exchange: binance
futures: false #futures: true
#margin: true
#isolatedMargin: true
#isolatedMarginSymbol: BTCUSDT
envVarPrefix: binance envVarPrefix: binance
heikinAshi: false heikinAshi: false
@ -23,34 +26,35 @@ exchangeStrategies:
- on: binance - on: binance
drift: drift:
limitOrder: false
canvasPath: "./output.png" canvasPath: "./output.png"
symbol: BTCUSDT symbol: BTCUSDT
# kline interval for indicators # kline interval for indicators
interval: 4m interval: 4m
window: 1 window: 2
stoploss: 0.22% stoploss: 0.13%
source: hl2 source: ohlc4
predictOffset: 2 predictOffset: 2
noTrailingStopLoss: false noTrailingStopLoss: false
trailingStopLossType: realtime trailingStopLossType: kline
# stddev on high/low-source # stddev on high/low-source
hlVarianceMultiplier: 0.01 hlVarianceMultiplier: 0.22
hlRangeWindow: 5 hlRangeWindow: 4
smootherWindow: 2 smootherWindow: 1
fisherTransformWindow: 27 fisherTransformWindow: 96
window1m: 58 window1m: 8
smootherWindow1m: 118 smootherWindow1m: 4
fisherTransformWindow1m: 319 fisherTransformWindow1m: 320
atrWindow: 14 atrWindow: 14
# orders not been traded will be canceled after `pendingMinutes` minutes # orders not been traded will be canceled after `pendingMinutes` minutes
pendingMinutes: 2 pendingMinutes: 10
noRebalance: true noRebalance: true
trendWindow: 576 trendWindow: 576
rebalanceFilter: 0 rebalanceFilter: 1.2
driftFilterPos: 0.6 #driftFilterPos: 0.5
driftFilterNeg: -0.6 #driftFilterNeg: -0.5
ddriftFilterPos: 0.00008 #ddriftFilterPos: 0.0008
ddriftFilterNeg: -0.00008 #ddriftFilterNeg: -0.0008
# 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
@ -135,4 +139,4 @@ backtest:
takerFeeRate: 0.000 takerFeeRate: 0.000
balances: balances:
BTC: 0 BTC: 0
USDT: 21 USDT: 1000

View File

@ -197,17 +197,14 @@ type OpenPositionOptions struct {
// Leverage is used for leveraged position and account // Leverage is used for leveraged position and account
// Leverage is not effected when using non-leverage spot account // Leverage is not effected when using non-leverage spot account
Leverage fixedpoint.Value `json:"leverage,omitempty"` Leverage fixedpoint.Value `json:"leverage,omitempty" modifiable:"true"`
// Quantity will be used first, it will override the leverage if it's given // Quantity will be used first, it will override the leverage if it's given
Quantity fixedpoint.Value `json:"quantity,omitempty"` Quantity fixedpoint.Value `json:"quantity,omitempty"`
// MarketOrder set to true to open a position with a market order
// default is MarketOrder = true
MarketOrder bool `json:"marketOrder,omitempty"`
// LimitOrder set to true to open a position with a limit order // LimitOrder set to true to open a position with a limit order
LimitOrder bool `json:"limitOrder,omitempty"` // default is false, and will send MarketOrder
LimitOrder bool `json:"limitOrder,omitempty" modifiable:"true"`
// LimitOrderTakerRatio is used when LimitOrder = true, it adjusts the price of the limit order with a ratio. // LimitOrderTakerRatio is used when LimitOrder = true, it adjusts the price of the limit order with a ratio.
// So you can ensure that the limit order can be a taker order. Higher the ratio, higher the chance it could be a taker order. // So you can ensure that the limit order can be a taker order. Higher the ratio, higher the chance it could be a taker order.
@ -222,7 +219,9 @@ type OpenPositionOptions struct {
Tags []string `json:"-" yaml:"-"` Tags []string `json:"-" yaml:"-"`
} }
func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPositionOptions) error { var Delta fixedpoint.Value = fixedpoint.NewFromFloat(0.005)
func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPositionOptions) (types.OrderSlice, error) {
price := options.Price price := options.Price
submitOrder := types.SubmitOrder{ submitOrder := types.SubmitOrder{
Symbol: e.position.Symbol, Symbol: e.position.Symbol,
@ -246,24 +245,34 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
} }
} }
if options.MarketOrder { if options.LimitOrder {
submitOrder.Type = types.OrderTypeMarket
} else if options.LimitOrder {
submitOrder.Type = types.OrderTypeLimit submitOrder.Type = types.OrderTypeLimit
submitOrder.Price = price submitOrder.Price = price
} }
quantity := options.Quantity quantity := options.Quantity
market, ok := e.session.Market(e.symbol)
if !ok {
return nil, errors.New("cannot find market with symbol " + e.symbol)
}
if options.Long { if options.Long {
if quantity.IsZero() { if quantity.IsZero() {
quoteQuantity, err := CalculateQuoteQuantity(ctx, e.session, e.position.QuoteCurrency, options.Leverage) quoteQuantity, err := CalculateQuoteQuantity(ctx, e.session, e.position.QuoteCurrency, options.Leverage)
if err != nil { if quoteQuantity.IsZero() {
return err log.Warnf("dust quantity: %v", quantity)
return nil, nil
}
if err != nil {
return nil, err
} }
quantity = quoteQuantity.Div(price) quantity = quoteQuantity.Div(price)
} }
if market.IsDustQuantity(quantity, price) {
log.Warnf("dust quantity: %v", quantity)
return nil, nil
}
quoteQuantity := quantity.Mul(price) quoteQuantity := quantity.Mul(price)
if e.session.Margin && !e.marginQuoteMaxBorrowable.IsZero() && quoteQuantity.Compare(e.marginQuoteMaxBorrowable) > 0 { if e.session.Margin && !e.marginQuoteMaxBorrowable.IsZero() && quoteQuantity.Compare(e.marginQuoteMaxBorrowable) > 0 {
@ -274,21 +283,34 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
submitOrder.Side = types.SideTypeBuy submitOrder.Side = types.SideTypeBuy
submitOrder.Quantity = quantity submitOrder.Quantity = quantity
Notify("Opening %s long position with quantity %f at price %f", e.position.Symbol, quantity.Float64(), price.Float64()) Notify("Opening %s long position with quantity %v at price %v", e.position.Symbol, quantity, price)
createdOrder, err2 := e.SubmitOrders(ctx, submitOrder) for {
if err2 != nil { createdOrder, err2 := e.SubmitOrders(ctx, submitOrder)
return err2 if err2 != nil {
submitOrder.Quantity = submitOrder.Quantity.Mul(fixedpoint.One.Sub(Delta))
if market.IsDustQuantity(submitOrder.Quantity, price) {
return nil, err2
}
continue
}
log.Infof("created order: %+v", createdOrder)
return createdOrder, nil
} }
log.Infof("created order: %+v", createdOrder)
return nil
} else if options.Short { } else if options.Short {
if quantity.IsZero() { if quantity.IsZero() {
var err error var err error
quantity, err = CalculateBaseQuantity(e.session, e.position.Market, price, quantity, options.Leverage) quantity, err = CalculateBaseQuantity(e.session, e.position.Market, price, quantity, options.Leverage)
if err != nil { if quantity.IsZero() {
return err log.Warnf("dust quantity: %v", quantity)
return nil, nil
} }
if err != nil {
return nil, err
}
}
if market.IsDustQuantity(quantity, price) {
log.Warnf("dust quantity: %v", quantity)
return nil, nil
} }
if e.session.Margin && !e.marginBaseMaxBorrowable.IsZero() && quantity.Sub(baseBalance.Available).Compare(e.marginBaseMaxBorrowable) > 0 { if e.session.Margin && !e.marginBaseMaxBorrowable.IsZero() && quantity.Sub(baseBalance.Available).Compare(e.marginBaseMaxBorrowable) > 0 {
@ -300,17 +322,22 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
submitOrder.Side = types.SideTypeSell submitOrder.Side = types.SideTypeSell
submitOrder.Quantity = quantity submitOrder.Quantity = quantity
Notify("Opening %s short position with quantity %f at price %f", e.position.Symbol, quantity.Float64(), price.Float64()) Notify("Opening %s short position with quantity %v at price %v", e.position.Symbol, quantity, price)
createdOrder, err2 := e.SubmitOrders(ctx, submitOrder) for {
if err2 != nil { createdOrder, err2 := e.SubmitOrders(ctx, submitOrder)
return err2 if err2 != nil {
submitOrder.Quantity = submitOrder.Quantity.Mul(fixedpoint.One.Sub(Delta))
if market.IsDustQuantity(submitOrder.Quantity, price) {
return nil, err2
}
continue
}
log.Infof("created order: %+v", createdOrder)
return createdOrder, nil
} }
log.Infof("created order: %+v", createdOrder)
return nil
} }
return errors.New("options Long or Short must be set") return nil, errors.New("options Long or Short must be set")
} }
// GracefulCancelActiveOrderBook cancels the orders from the active orderbook. // GracefulCancelActiveOrderBook cancels the orders from the active orderbook.

View File

@ -29,11 +29,20 @@ func LookupSymbolField(rs reflect.Value) (string, bool) {
// Used by bbgo/interact_modify.go // Used by bbgo/interact_modify.go
func GetModifiableFields(val reflect.Value, callback func(tagName, name string)) { func GetModifiableFields(val reflect.Value, callback func(tagName, name string)) {
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if !val.IsValid() {
return
}
for i := 0; i < val.Type().NumField(); i++ { for i := 0; i < val.Type().NumField(); i++ {
t := val.Type().Field(i) t := val.Type().Field(i)
if !t.IsExported() { if !t.IsExported() {
continue continue
} }
if t.Anonymous {
GetModifiableFields(val.Field(i), callback)
}
modifiable := t.Tag.Get("modifiable") modifiable := t.Tag.Get("modifiable")
if modifiable != "true" { if modifiable != "true" {
continue continue
@ -50,6 +59,17 @@ func GetModifiableFields(val reflect.Value, callback func(tagName, name string))
var zeroValue reflect.Value = reflect.Zero(reflect.TypeOf(0)) var zeroValue reflect.Value = reflect.Zero(reflect.TypeOf(0))
func GetModifiableField(val reflect.Value, name string) (reflect.Value, bool) { func GetModifiableField(val reflect.Value, name string) (reflect.Value, bool) {
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return zeroValue, false
}
}
if val.Kind() != reflect.Struct {
return zeroValue, false
}
if !val.IsValid() {
return zeroValue, false
}
field, ok := val.Type().FieldByName(name) field, ok := val.Type().FieldByName(name)
if !ok { if !ok {
return zeroValue, ok return zeroValue, ok
@ -61,5 +81,9 @@ func GetModifiableField(val reflect.Value, name string) (reflect.Value, bool) {
if jsonTag == "" || jsonTag == "-" { if jsonTag == "" || jsonTag == "-" {
return zeroValue, false return zeroValue, false
} }
return val.FieldByName(name), true value, err := val.FieldByIndexErr(field.Index)
if err != nil {
return zeroValue, false
}
return value, true
} }

View File

@ -2,6 +2,7 @@ package dynamic
import ( import (
"encoding/json" "encoding/json"
"fmt"
"reflect" "reflect"
"testing" "testing"
@ -9,7 +10,17 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
type Inner struct {
Field5 float64 `json:"field5,omitempty" modifiable:"true"`
}
type InnerPointer struct {
Field6 float64 `json:"field6" modifiable:"true"`
}
type Strategy struct { type Strategy struct {
Inner
*InnerPointer
Field1 fixedpoint.Value `json:"field1" modifiable:"true"` Field1 fixedpoint.Value `json:"field1" modifiable:"true"`
Field2 float64 `json:"field2"` Field2 float64 `json:"field2"`
field3 float64 `json:"field3" modifiable:"true"` field3 float64 `json:"field3" modifiable:"true"`
@ -24,7 +35,7 @@ func TestGetModifiableFields(t *testing.T) {
assert.NotEqual(t, name, "Field2") assert.NotEqual(t, name, "Field2")
assert.NotEqual(t, tagName, "field3") assert.NotEqual(t, tagName, "field3")
assert.NotEqual(t, name, "Field3") assert.NotEqual(t, name, "Field3")
fmt.Println(tagName, name)
}) })
} }
@ -34,6 +45,13 @@ func TestGetModifiableField(t *testing.T) {
val := reflect.ValueOf(s).Elem() val := reflect.ValueOf(s).Elem()
_, ok := GetModifiableField(val, "Field1") _, ok := GetModifiableField(val, "Field1")
assert.True(t, ok) assert.True(t, ok)
_, ok = GetModifiableField(val, "Field5")
assert.True(t, ok)
_, ok = GetModifiableField(val, "Field6")
assert.False(t, ok)
s.InnerPointer = &InnerPointer{}
_, ok = GetModifiableField(val, "Field6")
assert.True(t, ok)
_, ok = GetModifiableField(val, "Field2") _, ok = GetModifiableField(val, "Field2")
assert.False(t, ok) assert.False(t, ok)
_, ok = GetModifiableField(val, "Field3") _, ok = GetModifiableField(val, "Field3")

View File

@ -3,5 +3,5 @@ package fixedpoint
var ( var (
Two Value = NewFromInt(2) Two Value = NewFromInt(2)
Three Value = NewFromInt(3) Three Value = NewFromInt(3)
Four Value = NewFromInt(3) Four Value = NewFromInt(4)
) )

View File

@ -39,6 +39,7 @@ func init() {
type Strategy struct { type Strategy struct {
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
bbgo.OpenPositionOptions
bbgo.StrategyController bbgo.StrategyController
types.Market types.Market
types.IntervalWindow types.IntervalWindow
@ -70,6 +71,7 @@ type Strategy struct {
beta float64 beta float64
Leverage fixedpoint.Value `json:"leverage" modifiable:"true"`
StopLoss fixedpoint.Value `json:"stoploss" modifiable:"true"` StopLoss fixedpoint.Value `json:"stoploss" modifiable:"true"`
CanvasPath string `json:"canvasPath"` CanvasPath string `json:"canvasPath"`
PredictOffset int `json:"predictOffset"` PredictOffset int `json:"predictOffset"`
@ -90,10 +92,10 @@ type Strategy struct {
TrailingCallbackRate []float64 `json:"trailingCallbackRate" modifiable:"true"` TrailingCallbackRate []float64 `json:"trailingCallbackRate" modifiable:"true"`
TrailingActivationRatio []float64 `json:"trailingActivationRatio" modifiable:"true"` TrailingActivationRatio []float64 `json:"trailingActivationRatio" modifiable:"true"`
DriftFilterNeg float64 `json:"driftFilterNeg" modifiable:"true"` DriftFilterNeg float64 //`json:"driftFilterNeg" modifiable:"true"`
DriftFilterPos float64 `json:"driftFilterPos" modifiable:"true"` DriftFilterPos float64 //`json:"driftFilterPos" modifiable:"true"`
DDriftFilterNeg float64 `json:"ddriftFilterNeg" modifiable:"true"` DDriftFilterNeg float64 //`json:"ddriftFilterNeg" modifiable:"true"`
DDriftFilterPos float64 `json:"ddriftFilterPos" modifiable:"true"` DDriftFilterPos float64 //`json:"ddriftFilterPos" modifiable:"true"`
buyPrice float64 `persistence:"buy_price"` buyPrice float64 `persistence:"buy_price"`
sellPrice float64 `persistence:"sell_price"` sellPrice float64 `persistence:"sell_price"`
@ -147,10 +149,11 @@ func (s *Strategy) CurrentPosition() *types.Position {
return s.Position return s.Position
} }
func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error { func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) bool {
order := s.p.NewMarketCloseOrder(percentage) order := s.p.NewMarketCloseOrder(percentage)
if order == nil { if order == nil {
return nil s.positionLock.Unlock()
return false
} }
order.Tag = "close" order.Tag = "close"
order.TimeInForce = "" order.TimeInForce = ""
@ -165,16 +168,18 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu
} else if order.Side == types.SideTypeSell && order.Quantity.Compare(baseBalance) > 0 { } else if order.Side == types.SideTypeSell && order.Quantity.Compare(baseBalance) > 0 {
order.Quantity = baseBalance order.Quantity = baseBalance
} }
order.MarginSideEffect = types.SideEffectTypeAutoRepay
s.positionLock.Unlock()
for { for {
if s.Market.IsDustQuantity(order.Quantity, price) { if s.Market.IsDustQuantity(order.Quantity, price) {
return nil return false
} }
_, err := s.GeneralOrderExecutor.SubmitOrders(ctx, *order) _, err := s.GeneralOrderExecutor.SubmitOrders(ctx, *order)
if err != nil { if err != nil {
order.Quantity = order.Quantity.Mul(fixedpoint.One.Sub(Delta)) order.Quantity = order.Quantity.Mul(fixedpoint.One.Sub(Delta))
continue continue
} }
return nil return true
} }
} }
@ -217,6 +222,7 @@ func (s *Strategy) initIndicators(store *bbgo.SerialMarketDataStore) error {
if !ok || klinesLength == 0 { if !ok || klinesLength == 0 {
return errors.New("klines not exists") return errors.New("klines not exists")
} }
log.Infof("loaded %d klines", klinesLength)
for _, kline := range *klines { for _, kline := range *klines {
source := s.GetSource(&kline).Float64() source := s.GetSource(&kline).Float64()
high := kline.High.Float64() high := kline.High.Float64()
@ -237,6 +243,7 @@ func (s *Strategy) initIndicators(store *bbgo.SerialMarketDataStore) error {
if !ok || klinesLength == 0 { if !ok || klinesLength == 0 {
return errors.New("klines not exists") return errors.New("klines not exists")
} }
log.Infof("loaded %d klines1m", klinesLength)
for _, kline := range *klines { for _, kline := range *klines {
source := s.GetSource(&kline).Float64() source := s.GetSource(&kline).Float64()
s.drift1m.Update(source, kline.Volume.Abs().Float64()) s.drift1m.Update(source, kline.Volume.Abs().Float64())
@ -377,9 +384,9 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= pricef || exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= pricef ||
s.trailingCheck(pricef, "long")) s.trailingCheck(pricef, "long"))
if exitShortCondition || exitLongCondition { if exitShortCondition || exitLongCondition {
log.Infof("Close position by orderbook changes") if s.ClosePosition(ctx, fixedpoint.One) {
s.positionLock.Unlock() log.Infof("Close position by orderbook changes")
_ = s.ClosePosition(ctx, fixedpoint.One) }
} else { } else {
s.positionLock.Unlock() s.positionLock.Unlock()
} }
@ -616,7 +623,6 @@ func (s *Strategy) klineHandler1m(ctx context.Context, kline types.KLine) {
exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= lowf || exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= lowf ||
s.trailingCheck(lowf, "long") /* || s.drift1m.Last() < 0*/) s.trailingCheck(lowf, "long") /* || s.drift1m.Last() < 0*/)
if exitShortCondition || exitLongCondition { if exitShortCondition || exitLongCondition {
s.positionLock.Unlock()
_ = s.ClosePosition(ctx, fixedpoint.One) _ = s.ClosePosition(ctx, fixedpoint.One)
} else { } else {
s.positionLock.Unlock() s.positionLock.Unlock()
@ -629,7 +635,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
s.frameKLine.Set(&kline) s.frameKLine.Set(&kline)
source := s.GetSource(s.frameKLine) source := s.GetSource(&kline)
sourcef := source.Float64() sourcef := source.Float64()
s.priceLines.Update(sourcef) s.priceLines.Update(sourcef)
s.ma.Update(sourcef) s.ma.Update(sourcef)
@ -664,7 +670,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
stoploss := s.StopLoss.Float64() stoploss := s.StopLoss.Float64()
s.positionLock.Lock() s.positionLock.Lock()
log.Infof("highdiff: %3.2f ma: %.2f, close: %8v, high: %8v, low: %8v, time: %v %v", s.stdevHigh.Last(), s.ma.Last(), 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)
if s.lowestPrice > 0 && lowf < s.lowestPrice { if s.lowestPrice > 0 && lowf < s.lowestPrice {
s.lowestPrice = lowf s.lowestPrice = lowf
} }
@ -688,6 +694,18 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
s.Market.QuoteCurrency, s.Market.QuoteCurrency,
balances[s.Market.QuoteCurrency].String(), balances[s.Market.QuoteCurrency].String(),
) )
s.DriftFilterPos = s.drift.Filter(func(i int, v float64) bool {
return v >= 0
}, 30).Mean(30)
s.DriftFilterNeg = s.drift.Filter(func(i int, v float64) bool {
return v <= 0
}, 30).Mean(30)
s.DDriftFilterPos = s.drift.drift.Filter(func(i int, v float64) bool {
return v >= 0
}, 30).Mean(30)
s.DDriftFilterNeg = s.drift.drift.Filter(func(i int, v float64) bool {
return v <= 0
}, 30).Mean(30)
shortCondition := (drift[1] >= s.DriftFilterNeg || ddrift[1] >= 0) && (driftPred <= s.DDriftFilterNeg || ddriftPred <= 0) || drift[1] < 0 && drift[0] < 0 shortCondition := (drift[1] >= s.DriftFilterNeg || ddrift[1] >= 0) && (driftPred <= s.DDriftFilterNeg || ddriftPred <= 0) || drift[1] < 0 && drift[0] < 0
longCondition := (drift[1] <= s.DriftFilterPos || ddrift[1] <= 0) && (driftPred >= s.DDriftFilterPos || ddriftPred >= 0) || drift[1] > 0 && drift[0] > 0 longCondition := (drift[1] <= s.DriftFilterPos || ddrift[1] <= 0) && (driftPred >= s.DDriftFilterPos || ddriftPred >= 0) || drift[1] > 0 && drift[0] > 0
@ -709,49 +727,40 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
s.positionLock.Unlock() s.positionLock.Unlock()
return return
} }
s.positionLock.Unlock()
_ = s.ClosePosition(ctx, fixedpoint.One) _ = s.ClosePosition(ctx, fixedpoint.One)
} }
if longCondition { if longCondition {
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil { if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
log.WithError(err).Errorf("cannot cancel orders") log.WithError(err).Errorf("cannot cancel orders")
s.positionLock.Unlock() s.positionLock.Unlock()
return 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 {
source = price
}*/
source = fixedpoint.NewFromFloat(s.ma.Last() - s.stdevLow.Last()*s.HighLowVarianceMultiplier)
if source.Compare(price) > 0 { if source.Compare(price) > 0 {
source = price source = price
} }
sourcef = source.Float64() 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())
quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency)
if !ok {
log.Errorf("unable to get quoteCurrency")
s.positionLock.Unlock()
return
}
if s.Market.IsDustQuantity(
quoteBalance.Available.Div(source), source) {
s.positionLock.Unlock()
return
}
s.positionLock.Unlock() s.positionLock.Unlock()
quantity := quoteBalance.Available.Div(source) opt := s.OpenPositionOptions
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{ opt.Long = true
Symbol: s.Symbol, opt.Price = source
Side: types.SideTypeBuy, opt.Tags = []string{"long"}
Type: types.OrderTypeLimit, createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
Price: source,
Quantity: quantity,
Tag: "long",
})
log.Infof("orders %v", createdOrders) log.Infof("orders %v", createdOrders)
if err != nil { if err != nil {
log.WithError(err).Errorf("cannot place buy order") log.WithError(err).Errorf("cannot place buy order")
return return
} }
s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter if createdOrders != nil {
s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter
}
return return
} }
if shortCondition { if shortCondition {
@ -760,13 +769,11 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
s.positionLock.Unlock() s.positionLock.Unlock()
return return
} }
baseBalance, ok := s.Session.GetAccount().Balance(s.Market.BaseCurrency) /*source = source.Add(fixedpoint.NewFromFloat(s.stdevHigh.Last() * s.HighLowVarianceMultiplier))
if !ok { if source.Compare(price) < 0 {
log.Errorf("unable to get baseBalance") source = price
s.positionLock.Unlock() }*/
return source = fixedpoint.NewFromFloat(s.ma.Last() + 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
} }
@ -774,32 +781,29 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
log.Infof("source in short: %v", source) log.Infof("source in short: %v", source)
if s.Market.IsDustQuantity(baseBalance.Available, source) {
s.positionLock.Unlock()
return
}
s.positionLock.Unlock() s.positionLock.Unlock()
// Cleanup pending StopOrders opt := s.OpenPositionOptions
quantity := baseBalance.Available opt.Short = true
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{ opt.Price = source
Symbol: s.Symbol, opt.Tags = []string{"long"}
Side: types.SideTypeSell, createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
Type: types.OrderTypeLimit, log.Infof("orders %v", createdOrders)
Price: source,
Quantity: quantity,
Tag: "short",
})
if err != nil { if err != nil {
log.WithError(err).Errorf("cannot place sell order") log.WithError(err).Errorf("cannot place buy order")
return return
} }
s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter if createdOrders != nil {
s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter
}
return return
} }
s.positionLock.Unlock() 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 {
if s.Leverage == fixedpoint.Zero {
s.Leverage = fixedpoint.One
}
instanceID := s.InstanceID() instanceID := s.InstanceID()
// Will be set by persistence if there's any from DB // Will be set by persistence if there's any from DB
if s.Position == nil { if s.Position == nil {
@ -885,15 +889,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.highestPrice = 0 s.highestPrice = 0
s.lowestPrice = 0 s.lowestPrice = 0
} else if s.p.IsLong() { } else if s.p.IsLong() {
s.buyPrice = trade.Price.Float64()
s.sellPrice = 0 s.sellPrice = 0
s.highestPrice = s.buyPrice
s.lowestPrice = 0 s.lowestPrice = 0
} else { } else {
s.sellPrice = trade.Price.Float64()
s.buyPrice = 0 s.buyPrice = 0
s.highestPrice = 0 s.highestPrice = 0
s.lowestPrice = s.sellPrice
} }
} else if tag == "long" { } else if tag == "long" {
if s.p.IsDust(trade.Price) { if s.p.IsDust(trade.Price) {

View File

@ -230,7 +230,7 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
opts.Price = previousLow.Mul(fixedpoint.One.Add(s.BounceRatio)) opts.Price = previousLow.Mul(fixedpoint.One.Add(s.BounceRatio))
} }
if err := s.orderExecutor.OpenPosition(ctx, opts); err != nil { if _, err := s.orderExecutor.OpenPosition(ctx, opts); err != nil {
log.WithError(err).Errorf("failed to open short position") log.WithError(err).Errorf("failed to open short position")
} }
})) }))

View File

@ -289,7 +289,7 @@ func (s *FailedBreakHigh) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg
opts.Short = true opts.Short = true
opts.Price = closePrice opts.Price = closePrice
opts.Tags = []string{"FailedBreakHighMarket"} opts.Tags = []string{"FailedBreakHighMarket"}
if err := s.orderExecutor.OpenPosition(ctx, opts); err != nil { if _, err := s.orderExecutor.OpenPosition(ctx, opts); err != nil {
log.WithError(err).Errorf("failed to open short position") log.WithError(err).Errorf("failed to open short position")
} }
})) }))

View File

@ -112,6 +112,7 @@ type SeriesExtend interface {
Softmax(window int) SeriesExtend Softmax(window int) SeriesExtend
Entropy(window int) float64 Entropy(window int) float64
CrossEntropy(b Series, window int) float64 CrossEntropy(b Series, window int) float64
Filter(b func(i int, value float64) bool, length int) SeriesExtend
} }
type SeriesBase struct { type SeriesBase struct {
@ -997,6 +998,51 @@ func Rolling(a Series, window int) *RollingResult {
return &RollingResult{a, window} return &RollingResult{a, window}
} }
type FilterResult struct {
a Series
b func(int, float64) bool
length int
c []int
}
func (f *FilterResult) Last() float64 {
return f.Index(0)
}
func (f *FilterResult) Index(j int) float64 {
if j >= f.length {
return 0
}
if len(f.c) > j {
return f.a.Index(f.c[j])
}
l := f.a.Length()
k := len(f.c)
i := 0
if k > 0 {
i = f.c[k-1] + 1
}
for ; i < l; i++ {
tmp := f.a.Index(i)
if f.b(i, tmp) {
f.c = append(f.c, i)
if j == k {
return tmp
}
k++
}
}
return 0
}
func (f *FilterResult) Length() int {
return f.length
}
func Filter(a Series, b func(i int, value float64) bool, length int) SeriesExtend {
return NewSeries(&FilterResult{a, b, length, nil})
}
type SigmoidResult struct { type SigmoidResult struct {
a Series a Series
} }

View File

@ -170,3 +170,13 @@ func TestPlot(t *testing.T) {
//defer f.Close() //defer f.Close()
//ct.Render(chart.PNG, f) //ct.Render(chart.PNG, f)
} }
func TestFilter(t *testing.T) {
a := floats.Slice{200., -200, 0, 1000, -100}
b := Filter(&a, func(i int, val float64) bool {
return val > 0
}, 4)
assert.Equal(t, b.Length(), 4)
assert.Equal(t, b.Last(), 1000.)
assert.Equal(t, b.Sum(3), 1200.)
}

View File

@ -146,3 +146,7 @@ func (s *SeriesBase) Entropy(window int) float64 {
func (s *SeriesBase) CrossEntropy(b Series, window int) float64 { func (s *SeriesBase) CrossEntropy(b Series, window int) float64 {
return CrossEntropy(s, b, window) return CrossEntropy(s, b, window)
} }
func (s *SeriesBase) Filter(b func(int, float64) bool, length int) SeriesExtend {
return Filter(s, b, length)
}