mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 16:55:15 +00:00
Merge pull request #964 from zenixls2/fix/order_executor_general
a lot of fixes in api / const
This commit is contained in:
commit
8efc948046
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,25 @@ type OpenPositionOptions struct {
|
||||||
Tags []string `json:"-" yaml:"-"`
|
Tags []string `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPositionOptions) error {
|
// Delta used to modify the order to submit, especially for the market order
|
||||||
|
var QuantityReduceDelta fixedpoint.Value = fixedpoint.NewFromFloat(0.005)
|
||||||
|
|
||||||
|
func (e *GeneralOrderExecutor) reduceQuantityAndSubmitOrder(ctx context.Context, price fixedpoint.Value, submitOrder types.SubmitOrder) (types.OrderSlice, error) {
|
||||||
|
for {
|
||||||
|
createdOrder, err2 := e.SubmitOrders(ctx, submitOrder)
|
||||||
|
if err2 != nil {
|
||||||
|
submitOrder.Quantity = submitOrder.Quantity.Mul(fixedpoint.One.Sub(QuantityReduceDelta))
|
||||||
|
if e.position.Market.IsDustQuantity(submitOrder.Quantity, price) {
|
||||||
|
return nil, err2
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Infof("created order: %+v", createdOrder)
|
||||||
|
return createdOrder, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,9 +261,7 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -259,11 +272,15 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
|
||||||
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 err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
quantity = quoteQuantity.Div(price)
|
quantity = quoteQuantity.Div(price)
|
||||||
}
|
}
|
||||||
|
if e.position.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,22 +291,20 @@ 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)
|
return e.reduceQuantityAndSubmitOrder(ctx, price, submitOrder)
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
|
|
||||||
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 err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if e.position.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 {
|
||||||
log.Warnf("adjusting %f quantity according to the max margin base borrowable amount: %f", quantity.Float64(), e.marginBaseMaxBorrowable.Float64())
|
log.Warnf("adjusting %f quantity according to the max margin base borrowable amount: %f", quantity.Float64(), e.marginBaseMaxBorrowable.Float64())
|
||||||
|
@ -300,17 +315,11 @@ 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)
|
return e.reduceQuantityAndSubmitOrder(ctx, price, submitOrder)
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("created order: %+v", createdOrder)
|
return nil, errors.New("options Long or Short must be set")
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 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.
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
@ -243,7 +244,8 @@ func CalculateBaseQuantity(session *ExchangeSession, market types.Market, price,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return quantity, fmt.Errorf("quantity is zero, can not submit sell order, please check your quantity settings, your account balances: %+v", balances)
|
return quantity, types.NewZeroAssetError(
|
||||||
|
fmt.Errorf("quantity is zero, can not submit sell order, please check your quantity settings, your account balances: %+v", balances))
|
||||||
}
|
}
|
||||||
|
|
||||||
usdBalances, restBalances := usdFiatBalances(balances)
|
usdBalances, restBalances := usdFiatBalances(balances)
|
||||||
|
@ -329,7 +331,8 @@ func CalculateBaseQuantity(session *ExchangeSession, market types.Market, price,
|
||||||
return maxPositionQuantity, nil
|
return maxPositionQuantity, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return quantity, fmt.Errorf("quantity is zero, can not submit sell order, please check your settings")
|
return quantity, types.NewZeroAssetError(
|
||||||
|
errors.New("quantity is zero, can not submit sell order, please check your settings"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func CalculateQuoteQuantity(ctx context.Context, session *ExchangeSession, quoteCurrency string, leverage fixedpoint.Value) (fixedpoint.Value, error) {
|
func CalculateQuoteQuantity(ctx context.Context, session *ExchangeSession, quoteCurrency string, leverage fixedpoint.Value) (fixedpoint.Value, error) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Delta = 1e-9
|
const delta = 1e-9
|
||||||
|
|
||||||
func TestExponentialScale(t *testing.T) {
|
func TestExponentialScale(t *testing.T) {
|
||||||
// graph see: https://www.desmos.com/calculator/ip0ijbcbbf
|
// graph see: https://www.desmos.com/calculator/ip0ijbcbbf
|
||||||
|
@ -19,8 +19,8 @@ func TestExponentialScale(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "f(x) = 0.001000 * 1.002305 ^ (x - 1000.000000)", scale.String())
|
assert.Equal(t, "f(x) = 0.001000 * 1.002305 ^ (x - 1000.000000)", scale.String())
|
||||||
assert.InDelta(t, 0.001, scale.Call(1000.0), Delta)
|
assert.InDelta(t, 0.001, scale.Call(1000.0), delta)
|
||||||
assert.InDelta(t, 0.01, scale.Call(2000.0), Delta)
|
assert.InDelta(t, 0.01, scale.Call(2000.0), delta)
|
||||||
|
|
||||||
for x := 1000; x <= 2000; x += 100 {
|
for x := 1000; x <= 2000; x += 100 {
|
||||||
y := scale.Call(float64(x))
|
y := scale.Call(float64(x))
|
||||||
|
@ -38,8 +38,8 @@ func TestExponentialScale_Reverse(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "f(x) = 0.100000 * 0.995405 ^ (x - 1000.000000)", scale.String())
|
assert.Equal(t, "f(x) = 0.100000 * 0.995405 ^ (x - 1000.000000)", scale.String())
|
||||||
assert.InDelta(t, 0.1, scale.Call(1000.0), Delta)
|
assert.InDelta(t, 0.1, scale.Call(1000.0), delta)
|
||||||
assert.InDelta(t, 0.001, scale.Call(2000.0), Delta)
|
assert.InDelta(t, 0.001, scale.Call(2000.0), delta)
|
||||||
|
|
||||||
for x := 1000; x <= 2000; x += 100 {
|
for x := 1000; x <= 2000; x += 100 {
|
||||||
y := scale.Call(float64(x))
|
y := scale.Call(float64(x))
|
||||||
|
@ -57,8 +57,8 @@ func TestLogScale(t *testing.T) {
|
||||||
err := scale.Solve()
|
err := scale.Solve()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "f(x) = 0.001303 * log(x - 999.000000) + 0.001000", scale.String())
|
assert.Equal(t, "f(x) = 0.001303 * log(x - 999.000000) + 0.001000", scale.String())
|
||||||
assert.InDelta(t, 0.001, scale.Call(1000.0), Delta)
|
assert.InDelta(t, 0.001, scale.Call(1000.0), delta)
|
||||||
assert.InDelta(t, 0.01, scale.Call(2000.0), Delta)
|
assert.InDelta(t, 0.01, scale.Call(2000.0), delta)
|
||||||
for x := 1000; x <= 2000; x += 100 {
|
for x := 1000; x <= 2000; x += 100 {
|
||||||
y := scale.Call(float64(x))
|
y := scale.Call(float64(x))
|
||||||
t.Logf("%s = %f", scale.FormulaOf(float64(x)), y)
|
t.Logf("%s = %f", scale.FormulaOf(float64(x)), y)
|
||||||
|
@ -74,8 +74,8 @@ func TestLinearScale(t *testing.T) {
|
||||||
err := scale.Solve()
|
err := scale.Solve()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "f(x) = 0.007000 * x + -4.000000", scale.String())
|
assert.Equal(t, "f(x) = 0.007000 * x + -4.000000", scale.String())
|
||||||
assert.InDelta(t, 3, scale.Call(1000), Delta)
|
assert.InDelta(t, 3, scale.Call(1000), delta)
|
||||||
assert.InDelta(t, 10, scale.Call(2000), Delta)
|
assert.InDelta(t, 10, scale.Call(2000), delta)
|
||||||
for x := 1000; x <= 2000; x += 100 {
|
for x := 1000; x <= 2000; x += 100 {
|
||||||
y := scale.Call(float64(x))
|
y := scale.Call(float64(x))
|
||||||
t.Logf("%s = %f", scale.FormulaOf(float64(x)), y)
|
t.Logf("%s = %f", scale.FormulaOf(float64(x)), y)
|
||||||
|
@ -91,8 +91,8 @@ func TestLinearScale2(t *testing.T) {
|
||||||
err := scale.Solve()
|
err := scale.Solve()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "f(x) = 0.150000 * x + -0.050000", scale.String())
|
assert.Equal(t, "f(x) = 0.150000 * x + -0.050000", scale.String())
|
||||||
assert.InDelta(t, 0.1, scale.Call(1), Delta)
|
assert.InDelta(t, 0.1, scale.Call(1), delta)
|
||||||
assert.InDelta(t, 0.4, scale.Call(3), Delta)
|
assert.InDelta(t, 0.4, scale.Call(3), delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQuadraticScale(t *testing.T) {
|
func TestQuadraticScale(t *testing.T) {
|
||||||
|
@ -105,9 +105,9 @@ func TestQuadraticScale(t *testing.T) {
|
||||||
err := scale.Solve()
|
err := scale.Solve()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "f(x) = 0.000550 * x ^ 2 + 0.135000 * x + 1.000000", scale.String())
|
assert.Equal(t, "f(x) = 0.000550 * x ^ 2 + 0.135000 * x + 1.000000", scale.String())
|
||||||
assert.InDelta(t, 1, scale.Call(0), Delta)
|
assert.InDelta(t, 1, scale.Call(0), delta)
|
||||||
assert.InDelta(t, 20, scale.Call(100.0), Delta)
|
assert.InDelta(t, 20, scale.Call(100.0), delta)
|
||||||
assert.InDelta(t, 50.0, scale.Call(200.0), Delta)
|
assert.InDelta(t, 50.0, scale.Call(200.0), delta)
|
||||||
for x := 0; x <= 200; x += 1 {
|
for x := 0; x <= 200; x += 1 {
|
||||||
y := scale.Call(float64(x))
|
y := scale.Call(float64(x))
|
||||||
t.Logf("%s = %f", scale.FormulaOf(float64(x)), y)
|
t.Logf("%s = %f", scale.FormulaOf(float64(x)), y)
|
||||||
|
@ -127,11 +127,11 @@ func TestPercentageScale(t *testing.T) {
|
||||||
|
|
||||||
v, err := s.Scale(0.0)
|
v, err := s.Scale(0.0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.InDelta(t, 1.0, v, Delta)
|
assert.InDelta(t, 1.0, v, delta)
|
||||||
|
|
||||||
v, err = s.Scale(1.0)
|
v, err = s.Scale(1.0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.InDelta(t, 100.0, v, Delta)
|
assert.InDelta(t, 100.0, v, delta)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("from -1.0 to 1.0", func(t *testing.T) {
|
t.Run("from -1.0 to 1.0", func(t *testing.T) {
|
||||||
|
@ -146,11 +146,11 @@ func TestPercentageScale(t *testing.T) {
|
||||||
|
|
||||||
v, err := s.Scale(-1.0)
|
v, err := s.Scale(-1.0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.InDelta(t, 10.0, v, Delta)
|
assert.InDelta(t, 10.0, v, delta)
|
||||||
|
|
||||||
v, err = s.Scale(1.0)
|
v, err = s.Scale(1.0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.InDelta(t, 100.0, v, Delta)
|
assert.InDelta(t, 100.0, v, delta)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("reverse -1.0 to 1.0", func(t *testing.T) {
|
t.Run("reverse -1.0 to 1.0", func(t *testing.T) {
|
||||||
|
@ -165,19 +165,19 @@ func TestPercentageScale(t *testing.T) {
|
||||||
|
|
||||||
v, err := s.Scale(-1.0)
|
v, err := s.Scale(-1.0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.InDelta(t, 100.0, v, Delta)
|
assert.InDelta(t, 100.0, v, delta)
|
||||||
|
|
||||||
v, err = s.Scale(1.0)
|
v, err = s.Scale(1.0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.InDelta(t, 10.0, v, Delta)
|
assert.InDelta(t, 10.0, v, delta)
|
||||||
|
|
||||||
v, err = s.Scale(2.0)
|
v, err = s.Scale(2.0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.InDelta(t, 10.0, v, Delta)
|
assert.InDelta(t, 10.0, v, delta)
|
||||||
|
|
||||||
v, err = s.Scale(-2.0)
|
v, err = s.Scale(-2.0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.InDelta(t, 100.0, v, Delta)
|
assert.InDelta(t, 100.0, v, delta)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("negative range", func(t *testing.T) {
|
t.Run("negative range", func(t *testing.T) {
|
||||||
|
@ -192,10 +192,10 @@ func TestPercentageScale(t *testing.T) {
|
||||||
|
|
||||||
v, err := s.Scale(0.0)
|
v, err := s.Scale(0.0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.InDelta(t, -100.0, v, Delta)
|
assert.InDelta(t, -100.0, v, delta)
|
||||||
|
|
||||||
v, err = s.Scale(1.0)
|
v, err = s.Scale(1.0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.InDelta(t, 100.0, v, Delta)
|
assert.InDelta(t, 100.0, v, delta)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package dynamic
|
package dynamic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -29,11 +30,21 @@ 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)) {
|
||||||
for i := 0; i < val.Type().NumField(); i++ {
|
if val.Kind() == reflect.Ptr {
|
||||||
|
val = val.Elem()
|
||||||
|
}
|
||||||
|
if !val.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
num := val.Type().NumField()
|
||||||
|
for i := 0; i < num; 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 +61,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 +83,31 @@ 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 := FieldByIndexErr(val, field.Index)
|
||||||
|
if err != nil {
|
||||||
|
return zeroValue, false
|
||||||
|
}
|
||||||
|
return value, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified from golang 1.19.1 reflect to eliminate all possible panic
|
||||||
|
func FieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) {
|
||||||
|
if len(index) == 1 {
|
||||||
|
return v.Field(index[0]), nil
|
||||||
|
}
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return zeroValue, errors.New("should receive a Struct")
|
||||||
|
}
|
||||||
|
for i, x := range index {
|
||||||
|
if i > 0 {
|
||||||
|
if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct {
|
||||||
|
if v.IsNil() {
|
||||||
|
return zeroValue, errors.New("reflect: indirection through nil pointer to embedded struct field ")
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = v.Field(x)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,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 +34,6 @@ 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")
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +43,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")
|
||||||
|
|
|
@ -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)
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
@ -90,10 +91,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 +148,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 +167,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 +221,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 +242,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 +383,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 +622,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 +634,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 +669,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 +693,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 +726,43 @@ 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()
|
s.positionLock.Unlock()
|
||||||
return
|
opt := s.OpenPositionOptions
|
||||||
}
|
opt.Long = true
|
||||||
if s.Market.IsDustQuantity(
|
opt.Price = source
|
||||||
quoteBalance.Available.Div(source), source) {
|
opt.Tags = []string{"long"}
|
||||||
s.positionLock.Unlock()
|
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
|
||||||
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",
|
|
||||||
})
|
|
||||||
log.Infof("orders %v", createdOrders)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := err.(types.ZeroAssetError); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
log.WithError(err).Errorf("cannot place buy order")
|
log.WithError(err).Errorf("cannot place buy order")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Infof("orders %v", createdOrders)
|
||||||
|
if createdOrders != nil {
|
||||||
s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter
|
s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if shortCondition {
|
if shortCondition {
|
||||||
|
@ -760,13 +771,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 +783,32 @@ 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()
|
s.positionLock.Unlock()
|
||||||
return
|
opt := s.OpenPositionOptions
|
||||||
}
|
opt.Short = true
|
||||||
s.positionLock.Unlock()
|
opt.Price = source
|
||||||
// Cleanup pending StopOrders
|
opt.Tags = []string{"long"}
|
||||||
quantity := baseBalance.Available
|
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
|
||||||
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
|
||||||
Symbol: s.Symbol,
|
|
||||||
Side: types.SideTypeSell,
|
|
||||||
Type: types.OrderTypeLimit,
|
|
||||||
Price: source,
|
|
||||||
Quantity: quantity,
|
|
||||||
Tag: "short",
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Errorf("cannot place sell order")
|
if _, ok := err.(types.ZeroAssetError); ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.WithError(err).Errorf("cannot place buy order")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Infof("orders %v", createdOrders)
|
||||||
|
if createdOrders != nil {
|
||||||
s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter
|
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 +894,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) {
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -21,3 +21,13 @@ func NewOrderError(e error, o Order) error {
|
||||||
order: o,
|
order: o,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ZeroAssetError struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewZeroAssetError(e error) ZeroAssetError {
|
||||||
|
return ZeroAssetError{
|
||||||
|
error: e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
51
pkg/types/filter.go
Normal file
51
pkg/types/filter.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter function filters Series by using a boolean function.
|
||||||
|
// When the boolean function returns true, the Series value at index i will be included in the returned Series.
|
||||||
|
// The returned Series will find at most `length` latest matching elements from the input Series.
|
||||||
|
// Query index larger or equal than length from the returned Series will return 0 instead.
|
||||||
|
// Notice that any Update on the input Series will make the previously returned Series outdated.
|
||||||
|
func Filter(a Series, b func(i int, value float64) bool, length int) SeriesExtend {
|
||||||
|
return NewSeries(&FilterResult{a, b, length, nil})
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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.)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user