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

View File

@ -8,7 +8,10 @@ persistence:
sessions:
binance:
exchange: binance
futures: false
#futures: true
#margin: true
#isolatedMargin: true
#isolatedMarginSymbol: BTCUSDT
envVarPrefix: binance
heikinAshi: false
@ -23,34 +26,35 @@ exchangeStrategies:
- on: binance
drift:
limitOrder: false
canvasPath: "./output.png"
symbol: BTCUSDT
# kline interval for indicators
interval: 4m
window: 1
stoploss: 0.22%
source: hl2
window: 2
stoploss: 0.13%
source: ohlc4
predictOffset: 2
noTrailingStopLoss: false
trailingStopLossType: realtime
trailingStopLossType: kline
# stddev on high/low-source
hlVarianceMultiplier: 0.01
hlRangeWindow: 5
smootherWindow: 2
fisherTransformWindow: 27
window1m: 58
smootherWindow1m: 118
fisherTransformWindow1m: 319
hlVarianceMultiplier: 0.22
hlRangeWindow: 4
smootherWindow: 1
fisherTransformWindow: 96
window1m: 8
smootherWindow1m: 4
fisherTransformWindow1m: 320
atrWindow: 14
# orders not been traded will be canceled after `pendingMinutes` minutes
pendingMinutes: 2
pendingMinutes: 10
noRebalance: true
trendWindow: 576
rebalanceFilter: 0
driftFilterPos: 0.6
driftFilterNeg: -0.6
ddriftFilterPos: 0.00008
ddriftFilterNeg: -0.00008
rebalanceFilter: 1.2
#driftFilterPos: 0.5
#driftFilterNeg: -0.5
#ddriftFilterPos: 0.0008
#ddriftFilterNeg: -0.0008
# ActivationRatio should be increasing order
# 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
balances:
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 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 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 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.
// 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:"-"`
}
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
submitOrder := types.SubmitOrder{
Symbol: e.position.Symbol,
@ -246,24 +245,34 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
}
}
if options.MarketOrder {
submitOrder.Type = types.OrderTypeMarket
} else if options.LimitOrder {
if options.LimitOrder {
submitOrder.Type = types.OrderTypeLimit
submitOrder.Price = price
}
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 quantity.IsZero() {
quoteQuantity, err := CalculateQuoteQuantity(ctx, e.session, e.position.QuoteCurrency, options.Leverage)
if err != nil {
return err
if quoteQuantity.IsZero() {
log.Warnf("dust quantity: %v", quantity)
return nil, nil
}
if err != nil {
return nil, err
}
quantity = quoteQuantity.Div(price)
}
if market.IsDustQuantity(quantity, price) {
log.Warnf("dust quantity: %v", quantity)
return nil, nil
}
quoteQuantity := quantity.Mul(price)
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.Quantity = quantity
Notify("Opening %s long position with quantity %f at price %f", e.position.Symbol, quantity.Float64(), price.Float64())
createdOrder, err2 := e.SubmitOrders(ctx, submitOrder)
if err2 != nil {
return err2
Notify("Opening %s long position with quantity %v at price %v", e.position.Symbol, quantity, price)
for {
createdOrder, err2 := e.SubmitOrders(ctx, submitOrder)
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 {
if quantity.IsZero() {
var err error
quantity, err = CalculateBaseQuantity(e.session, e.position.Market, price, quantity, options.Leverage)
if err != nil {
return err
if quantity.IsZero() {
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 {
@ -300,17 +322,22 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
submitOrder.Side = types.SideTypeSell
submitOrder.Quantity = quantity
Notify("Opening %s short position with quantity %f at price %f", e.position.Symbol, quantity.Float64(), price.Float64())
createdOrder, err2 := e.SubmitOrders(ctx, submitOrder)
if err2 != nil {
return err2
Notify("Opening %s short position with quantity %v at price %v", e.position.Symbol, quantity, price)
for {
createdOrder, err2 := e.SubmitOrders(ctx, submitOrder)
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.

View File

@ -29,11 +29,20 @@ func LookupSymbolField(rs reflect.Value) (string, bool) {
// Used by bbgo/interact_modify.go
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++ {
t := val.Type().Field(i)
if !t.IsExported() {
continue
}
if t.Anonymous {
GetModifiableFields(val.Field(i), callback)
}
modifiable := t.Tag.Get("modifiable")
if modifiable != "true" {
continue
@ -50,6 +59,17 @@ func GetModifiableFields(val reflect.Value, callback func(tagName, name string))
var zeroValue reflect.Value = reflect.Zero(reflect.TypeOf(0))
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)
if !ok {
return zeroValue, ok
@ -61,5 +81,9 @@ func GetModifiableField(val reflect.Value, name string) (reflect.Value, bool) {
if jsonTag == "" || jsonTag == "-" {
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 (
"encoding/json"
"fmt"
"reflect"
"testing"
@ -9,7 +10,17 @@ import (
"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 {
Inner
*InnerPointer
Field1 fixedpoint.Value `json:"field1" modifiable:"true"`
Field2 float64 `json:"field2"`
field3 float64 `json:"field3" modifiable:"true"`
@ -24,7 +35,7 @@ func TestGetModifiableFields(t *testing.T) {
assert.NotEqual(t, name, "Field2")
assert.NotEqual(t, tagName, "field3")
assert.NotEqual(t, name, "Field3")
fmt.Println(tagName, name)
})
}
@ -34,6 +45,13 @@ func TestGetModifiableField(t *testing.T) {
val := reflect.ValueOf(s).Elem()
_, ok := GetModifiableField(val, "Field1")
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")
assert.False(t, ok)
_, ok = GetModifiableField(val, "Field3")

View File

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

View File

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

View File

@ -289,7 +289,7 @@ func (s *FailedBreakHigh) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg
opts.Short = true
opts.Price = closePrice
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")
}
}))

View File

@ -112,6 +112,7 @@ type SeriesExtend interface {
Softmax(window int) SeriesExtend
Entropy(window int) float64
CrossEntropy(b Series, window int) float64
Filter(b func(i int, value float64) bool, length int) SeriesExtend
}
type SeriesBase struct {
@ -997,6 +998,51 @@ func Rolling(a Series, window int) *RollingResult {
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 {
a Series
}

View File

@ -170,3 +170,13 @@ func TestPlot(t *testing.T) {
//defer f.Close()
//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 {
return CrossEntropy(s, b, window)
}
func (s *SeriesBase) Filter(b func(int, float64) bool, length int) SeriesExtend {
return Filter(s, b, length)
}