diff --git a/config/drift.yaml b/config/drift.yaml index 3d2a5a23e..fcbd29373 100644 --- a/config/drift.yaml +++ b/config/drift.yaml @@ -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 diff --git a/config/driftBTC.yaml b/config/driftBTC.yaml index 80b7a7b66..f2baf4d68 100644 --- a/config/driftBTC.yaml +++ b/config/driftBTC.yaml @@ -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 diff --git a/pkg/bbgo/order_executor_general.go b/pkg/bbgo/order_executor_general.go index decf0dc0b..645c74f12 100644 --- a/pkg/bbgo/order_executor_general.go +++ b/pkg/bbgo/order_executor_general.go @@ -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. diff --git a/pkg/dynamic/field.go b/pkg/dynamic/field.go index 990ad4982..66db7741b 100644 --- a/pkg/dynamic/field.go +++ b/pkg/dynamic/field.go @@ -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 } diff --git a/pkg/dynamic/field_test.go b/pkg/dynamic/field_test.go index bbdd39056..862f37380 100644 --- a/pkg/dynamic/field_test.go +++ b/pkg/dynamic/field_test.go @@ -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") diff --git a/pkg/fixedpoint/const.go b/pkg/fixedpoint/const.go index e00c3d1e3..86e63cd23 100644 --- a/pkg/fixedpoint/const.go +++ b/pkg/fixedpoint/const.go @@ -3,5 +3,5 @@ package fixedpoint var ( Two Value = NewFromInt(2) Three Value = NewFromInt(3) - Four Value = NewFromInt(3) + Four Value = NewFromInt(4) ) diff --git a/pkg/strategy/drift/strategy.go b/pkg/strategy/drift/strategy.go index 5d82174be..3cc612648 100644 --- a/pkg/strategy/drift/strategy.go +++ b/pkg/strategy/drift/strategy.go @@ -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) { diff --git a/pkg/strategy/pivotshort/breaklow.go b/pkg/strategy/pivotshort/breaklow.go index 42070f339..91f9f0eed 100644 --- a/pkg/strategy/pivotshort/breaklow.go +++ b/pkg/strategy/pivotshort/breaklow.go @@ -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") } })) diff --git a/pkg/strategy/pivotshort/failedbreakhigh.go b/pkg/strategy/pivotshort/failedbreakhigh.go index 28575f543..eb193e994 100644 --- a/pkg/strategy/pivotshort/failedbreakhigh.go +++ b/pkg/strategy/pivotshort/failedbreakhigh.go @@ -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") } })) diff --git a/pkg/types/indicator.go b/pkg/types/indicator.go index 4d78f129c..d71c88247 100644 --- a/pkg/types/indicator.go +++ b/pkg/types/indicator.go @@ -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 } diff --git a/pkg/types/indicator_test.go b/pkg/types/indicator_test.go index d14de03c6..7da2c17c5 100644 --- a/pkg/types/indicator_test.go +++ b/pkg/types/indicator_test.go @@ -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.) +} diff --git a/pkg/types/seriesbase_imp.go b/pkg/types/seriesbase_imp.go index b952f7e5c..98329ad29 100644 --- a/pkg/types/seriesbase_imp.go +++ b/pkg/types/seriesbase_imp.go @@ -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) +}