mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
refactor basic risk controller
This commit is contained in:
parent
ded89e099f
commit
4a2a542222
|
@ -25,7 +25,7 @@ riskControls:
|
||||||
# "max" is the session name that you want to configure the risk control
|
# "max" is the session name that you want to configure the risk control
|
||||||
max:
|
max:
|
||||||
# orderExecutors is one of the risk control
|
# orderExecutors is one of the risk control
|
||||||
orderExecutors:
|
orderExecutor:
|
||||||
# symbol-routed order executor
|
# symbol-routed order executor
|
||||||
bySymbol:
|
bySymbol:
|
||||||
BTCUSDT:
|
BTCUSDT:
|
||||||
|
|
|
@ -31,8 +31,8 @@ riskControls:
|
||||||
sessionBased:
|
sessionBased:
|
||||||
# "max" is the session name that you want to configure the risk control
|
# "max" is the session name that you want to configure the risk control
|
||||||
max:
|
max:
|
||||||
# orderExecutors is one of the risk control
|
# orderExecutor is one of the risk control
|
||||||
orderExecutors:
|
orderExecutor:
|
||||||
# symbol-routed order executor
|
# symbol-routed order executor
|
||||||
bySymbol:
|
bySymbol:
|
||||||
BTCUSDT:
|
BTCUSDT:
|
||||||
|
|
|
@ -31,8 +31,8 @@ riskControls:
|
||||||
sessionBased:
|
sessionBased:
|
||||||
# "max" is the session name that you want to configure the risk control
|
# "max" is the session name that you want to configure the risk control
|
||||||
max:
|
max:
|
||||||
# orderExecutors is one of the risk control
|
# orderExecutor is one of the risk control
|
||||||
orderExecutors:
|
orderExecutor:
|
||||||
# symbol-routed order executor
|
# symbol-routed order executor
|
||||||
bySymbol:
|
bySymbol:
|
||||||
BNBUSDT:
|
BNBUSDT:
|
||||||
|
|
|
@ -27,8 +27,8 @@ riskControls:
|
||||||
sessionBased:
|
sessionBased:
|
||||||
# "max" is the session name that you want to configure the risk control
|
# "max" is the session name that you want to configure the risk control
|
||||||
binance:
|
binance:
|
||||||
# orderExecutors is one of the risk control
|
# orderExecutor is one of the risk control
|
||||||
orderExecutors:
|
orderExecutor:
|
||||||
# symbol-routed order executor
|
# symbol-routed order executor
|
||||||
bySymbol:
|
bySymbol:
|
||||||
BNBUSDT:
|
BNBUSDT:
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -7,6 +7,7 @@ go 1.13
|
||||||
require (
|
require (
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||||
github.com/adshao/go-binance v0.0.0-20201015231210-37cee298310e
|
github.com/adshao/go-binance v0.0.0-20201015231210-37cee298310e
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
|
||||||
github.com/c9s/goose v0.0.0-20200415105707-8da682162a5b
|
github.com/c9s/goose v0.0.0-20200415105707-8da682162a5b
|
||||||
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
|
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
|
|
|
@ -2,10 +2,10 @@ package bbgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
@ -71,97 +71,161 @@ func (e *ExchangeOrderExecutor) SubmitOrders(ctx context.Context, orders ...type
|
||||||
return e.session.Exchange.SubmitOrders(ctx, formattedOrders...)
|
return e.session.Exchange.SubmitOrders(ctx, formattedOrders...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BasicRiskControlOrderExecutor struct {
|
type BasicRiskController struct {
|
||||||
*ExchangeOrderExecutor
|
Logger *logrus.Logger
|
||||||
|
|
||||||
MinQuoteBalance fixedpoint.Value `json:"minQuoteBalance,omitempty"`
|
MaxOrderAmount fixedpoint.Value `json:"maxOrderAmount,omitempty"`
|
||||||
MaxAssetBalance fixedpoint.Value `json:"maxBaseAssetBalance,omitempty"`
|
MinQuoteBalance fixedpoint.Value `json:"minQuoteBalance,omitempty"`
|
||||||
MinAssetBalance fixedpoint.Value `json:"minBaseAssetBalance,omitempty"`
|
MaxBaseAssetBalance fixedpoint.Value `json:"maxBaseAssetBalance,omitempty"`
|
||||||
MaxOrderAmount fixedpoint.Value `json:"maxOrderAmount,omitempty"`
|
MinBaseAssetBalance fixedpoint.Value `json:"minBaseAssetBalance,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *BasicRiskControlOrderExecutor) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (types.OrderSlice, error) {
|
// ProcessOrders filters and modifies the submit order objects by:
|
||||||
var formattedOrders []types.SubmitOrder
|
// 1. Increase the quantity by the minimal requirement
|
||||||
|
// 2. Decrease the quantity by risk controls
|
||||||
|
// 3. If the quantity does not meet minimal requirement, we should ignore the submit order.
|
||||||
|
func (c *BasicRiskController) ProcessOrders(session *ExchangeSession, orders ...types.SubmitOrder) (outOrders []types.SubmitOrder, err error) {
|
||||||
|
balances := session.Account.Balances()
|
||||||
|
|
||||||
|
accumulativeQuoteAmount := 0.0
|
||||||
|
accumulativeBaseSellQuantity := 0.0
|
||||||
for _, order := range orders {
|
for _, order := range orders {
|
||||||
currentPrice, ok := e.session.LastPrice(order.Symbol)
|
lastPrice, ok := session.LastPrice(order.Symbol)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.Errorf("the last price of symbol %q is not found", order.Symbol)
|
c.Logger.Errorf("the last price of symbol %q is not found", order.Symbol)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
market := order.Market
|
market, ok := session.Market(order.Symbol)
|
||||||
|
if !ok {
|
||||||
|
c.Logger.Errorf("the market config of symbol %q is not found", order.Symbol)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
price := order.Price
|
||||||
quantity := order.Quantity
|
quantity := order.Quantity
|
||||||
balances := e.session.Account.Balances()
|
switch order.Type {
|
||||||
|
case types.OrderTypeMarket:
|
||||||
|
price = lastPrice
|
||||||
|
}
|
||||||
|
|
||||||
switch order.Side {
|
switch order.Side {
|
||||||
case types.SideTypeBuy:
|
case types.SideTypeBuy:
|
||||||
|
// Critical conditions for placing buy orders
|
||||||
|
quoteBalance, ok := balances[market.QuoteCurrency]
|
||||||
|
if !ok {
|
||||||
|
c.Logger.Errorf("can not place buy order, quote balance %s not found", market.QuoteCurrency)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if balance, ok := balances[market.QuoteCurrency]; ok {
|
if quoteBalance.Available < c.MinQuoteBalance.Float64() {
|
||||||
if balance.Available < e.MinQuoteBalance.Float64() {
|
c.Logger.WithError(ErrQuoteBalanceLevelTooLow).Errorf("can not place buy order, quote balance level is too low: %s < %s",
|
||||||
return nil, errors.Wrapf(ErrQuoteBalanceLevelTooLow, "quote balance level is too low: %s < %s",
|
types.USD.FormatMoneyFloat64(quoteBalance.Available),
|
||||||
types.USD.FormatMoneyFloat64(balance.Available),
|
types.USD.FormatMoneyFloat64(c.MinQuoteBalance.Float64()))
|
||||||
types.USD.FormatMoneyFloat64(e.MinQuoteBalance.Float64()))
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase the quantity if the amount is not enough,
|
||||||
|
// this is the only increase op, later we will decrease the quantity if it meets the criteria
|
||||||
|
quantity = adjustQuantityByMinAmount(quantity, price, market.MinAmount*1.01)
|
||||||
|
|
||||||
|
if c.MaxOrderAmount > 0 {
|
||||||
|
quantity = adjustQuantityByMaxAmount(quantity, price, c.MaxOrderAmount.Float64())
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteAssetQuota := math.Max(0.0, quoteBalance.Available-c.MinQuoteBalance.Float64())
|
||||||
|
if quoteAssetQuota < market.MinAmount {
|
||||||
|
c.Logger.WithError(ErrInsufficientQuoteBalance).Errorf("can not place buy order, insufficient quote balance: quota %f < min amount %f", quoteAssetQuota, market.MinAmount)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
quantity = adjustQuantityByMaxAmount(quantity, price, quoteAssetQuota)
|
||||||
|
|
||||||
|
// if MaxBaseAssetBalance is enabled, we should check the current base asset balance
|
||||||
|
if baseBalance, hasBaseAsset := balances[market.BaseCurrency]; hasBaseAsset && c.MaxBaseAssetBalance > 0 {
|
||||||
|
if baseBalance.Available > c.MaxBaseAssetBalance.Float64() {
|
||||||
|
c.Logger.WithError(ErrAssetBalanceLevelTooHigh).Errorf("should not place buy order, asset balance level is too high: %f > %f", baseBalance.Available, c.MaxBaseAssetBalance.Float64())
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseBalance, ok := balances[market.BaseCurrency]; ok {
|
baseAssetQuota := math.Max(0, c.MaxBaseAssetBalance.Float64()-baseBalance.Available)
|
||||||
if e.MaxAssetBalance > 0 && baseBalance.Available > e.MaxAssetBalance.Float64() {
|
if quantity > baseAssetQuota {
|
||||||
return nil, errors.Wrapf(ErrAssetBalanceLevelTooHigh, "asset balance level is too high: %f > %f", baseBalance.Available, e.MaxAssetBalance.Float64())
|
quantity = baseAssetQuota
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
available := math.Max(0.0, balance.Available-e.MinQuoteBalance.Float64())
|
|
||||||
if available < market.MinAmount {
|
|
||||||
return nil, errors.Wrapf(ErrInsufficientQuoteBalance, "insufficient quote balance: %f < min amount %f", available, market.MinAmount)
|
|
||||||
}
|
|
||||||
|
|
||||||
quantity = adjustQuantityByMinAmount(quantity, currentPrice, market.MinAmount*1.01)
|
|
||||||
quantity = adjustQuantityByMaxAmount(quantity, currentPrice, available)
|
|
||||||
amount := quantity * currentPrice
|
|
||||||
if amount < market.MinAmount {
|
|
||||||
return nil, fmt.Errorf("amount too small: %f < min amount %f", amount, market.MinAmount)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the amount is still too small, we should skip it.
|
||||||
|
notional := quantity * lastPrice
|
||||||
|
if notional < market.MinAmount {
|
||||||
|
c.Logger.Errorf("can not place buy order, quote amount too small: notional %f < min amount %f", notional, market.MinAmount)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulativeQuoteAmount += notional
|
||||||
|
|
||||||
case types.SideTypeSell:
|
case types.SideTypeSell:
|
||||||
|
// Critical conditions for placing SELL orders
|
||||||
|
baseAssetBalance, ok := balances[market.BaseCurrency]
|
||||||
|
if !ok {
|
||||||
|
c.Logger.Errorf("can not place sell order, no base asset balance %s", market.BaseCurrency)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if balance, ok := balances[market.BaseCurrency]; ok {
|
// if the amount is too small, we should increase it.
|
||||||
if e.MinAssetBalance > 0 && balance.Available < e.MinAssetBalance.Float64() {
|
quantity = adjustQuantityByMinAmount(quantity, price, market.MinNotional*1.01)
|
||||||
return nil, errors.Wrapf(ErrAssetBalanceLevelTooLow, "asset balance level is too low: %f > %f", balance.Available, e.MinAssetBalance.Float64())
|
|
||||||
|
// we should not SELL too much
|
||||||
|
quantity = math.Min(quantity, baseAssetBalance.Available)
|
||||||
|
|
||||||
|
if c.MinBaseAssetBalance > 0 {
|
||||||
|
if baseAssetBalance.Available < c.MinBaseAssetBalance.Float64() {
|
||||||
|
c.Logger.WithError(ErrAssetBalanceLevelTooLow).Errorf("asset balance level is too low: %f > %f", baseAssetBalance.Available, c.MinBaseAssetBalance.Float64())
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
quantity = adjustQuantityByMinAmount(quantity, currentPrice, market.MinNotional*1.01)
|
quantity = math.Min(quantity, baseAssetBalance.Available-c.MinBaseAssetBalance.Float64())
|
||||||
|
|
||||||
available := balance.Available
|
|
||||||
quantity = math.Min(quantity, available)
|
|
||||||
if quantity < market.MinQuantity {
|
if quantity < market.MinQuantity {
|
||||||
return nil, errors.Wrapf(ErrInsufficientAssetBalance, "insufficient asset balance: %f > minimal quantity %f", available, market.MinQuantity)
|
c.Logger.WithError(ErrInsufficientAssetBalance).Errorf("insufficient asset balance: %f > minimal quantity %f", baseAssetBalance.Available, market.MinQuantity)
|
||||||
}
|
continue
|
||||||
|
|
||||||
notional := quantity * currentPrice
|
|
||||||
if notional < market.MinNotional {
|
|
||||||
return nil, fmt.Errorf("notional %f < min notional: %f", notional, market.MinNotional)
|
|
||||||
}
|
|
||||||
|
|
||||||
if quantity < market.MinLot {
|
|
||||||
return nil, fmt.Errorf("quantity %f less than min lot %f", quantity, market.MinLot)
|
|
||||||
}
|
|
||||||
|
|
||||||
notional = quantity * currentPrice
|
|
||||||
if notional < market.MinNotional {
|
|
||||||
return nil, fmt.Errorf("notional %f < min notional: %f", notional, market.MinNotional)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.MaxOrderAmount > 0 {
|
||||||
|
quantity = adjustQuantityByMaxAmount(quantity, price, c.MaxOrderAmount.Float64())
|
||||||
|
}
|
||||||
|
|
||||||
|
notional := quantity * lastPrice
|
||||||
|
if notional < market.MinNotional {
|
||||||
|
c.Logger.Errorf("can not place sell order, notional %f < min notional: %f", notional, market.MinNotional)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if quantity < market.MinLot {
|
||||||
|
c.Logger.Errorf("can not place sell order, quantity %f is less than the minimal lot %f", quantity, market.MinLot)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulativeBaseSellQuantity += quantity
|
||||||
}
|
}
|
||||||
|
|
||||||
// update quantity and format the order
|
// update quantity and format the order
|
||||||
order.Quantity = quantity
|
order.Quantity = quantity
|
||||||
o, err := formatOrder(order, e.session)
|
outOrders = append(outOrders, order)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
formattedOrders = append(formattedOrders, o)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return outOrders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasicRiskControlOrderExecutor struct {
|
||||||
|
*ExchangeOrderExecutor
|
||||||
|
|
||||||
|
BasicRiskController
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BasicRiskControlOrderExecutor) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (types.OrderSlice, error) {
|
||||||
|
orders, _ = e.BasicRiskController.ProcessOrders(e.session, orders...)
|
||||||
|
formattedOrders, _ := formatOrders(orders, e.session)
|
||||||
|
|
||||||
e.notifySubmitOrders(formattedOrders...)
|
e.notifySubmitOrders(formattedOrders...)
|
||||||
|
|
||||||
return e.session.Exchange.SubmitOrders(ctx, formattedOrders...)
|
return e.session.Exchange.SubmitOrders(ctx, formattedOrders...)
|
||||||
|
|
|
@ -33,8 +33,8 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseBalance, ok := tradingCtx.Balances[market.BaseCurrency]; ok {
|
if baseBalance, ok := tradingCtx.Balances[market.BaseCurrency]; ok {
|
||||||
if util.NotZero(p.MaxAssetBalance) && baseBalance.Available > p.MaxAssetBalance {
|
if util.NotZero(p.MaxBaseAssetBalance) && baseBalance.Available > p.MaxBaseAssetBalance {
|
||||||
return errors.Wrapf(ErrAssetBalanceLevelTooHigh, "asset balance level is too high: %f > %f", baseBalance.Available, p.MaxAssetBalance)
|
return errors.Wrapf(ErrAssetBalanceLevelTooHigh, "asset balance level is too high: %f > %f", baseBalance.Available, p.MaxBaseAssetBalance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,8 +55,8 @@ var (
|
||||||
case types.SideTypeSell:
|
case types.SideTypeSell:
|
||||||
|
|
||||||
if balance, ok := tradingCtx.Balances[market.BaseCurrency]; ok {
|
if balance, ok := tradingCtx.Balances[market.BaseCurrency]; ok {
|
||||||
if util.NotZero(p.MinAssetBalance) && balance.Available < p.MinAssetBalance {
|
if util.NotZero(p.MinBaseAssetBalance) && balance.Available < p.MinBaseAssetBalance {
|
||||||
return errors.Wrapf(ErrAssetBalanceLevelTooLow, "asset balance level is too low: %f > %f", balance.Available, p.MinAssetBalance)
|
return errors.Wrapf(ErrAssetBalanceLevelTooLow, "asset balance level is too low: %f > %f", balance.Available, p.MinBaseAssetBalance)
|
||||||
}
|
}
|
||||||
|
|
||||||
quantity = adjustQuantityByMinAmount(quantity, currentPrice, market.MinNotional*1.01)
|
quantity = adjustQuantityByMinAmount(quantity, currentPrice, market.MinNotional*1.01)
|
||||||
|
@ -101,7 +101,8 @@ var (
|
||||||
order.QuantityString = market.FormatVolume(quantity)
|
order.QuantityString = market.FormatVolume(quantity)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func adjustQuantityByMinAmount(quantity float64, currentPrice float64, minAmount float64) float64 {
|
// adjustQuantityByMinAmount adjusts the quantity to make the amount greater than the given minAmount
|
||||||
|
func adjustQuantityByMinAmount(quantity, currentPrice, minAmount float64) float64 {
|
||||||
// modify quantity for the min amount
|
// modify quantity for the min amount
|
||||||
amount := currentPrice * quantity
|
amount := currentPrice * quantity
|
||||||
if amount < minAmount {
|
if amount < minAmount {
|
||||||
|
@ -112,8 +113,8 @@ func adjustQuantityByMinAmount(quantity float64, currentPrice float64, minAmount
|
||||||
return quantity
|
return quantity
|
||||||
}
|
}
|
||||||
|
|
||||||
func adjustQuantityByMaxAmount(quantity float64, currentPrice float64, maxAmount float64) float64 {
|
func adjustQuantityByMaxAmount(quantity float64, price float64, maxAmount float64) float64 {
|
||||||
amount := currentPrice * quantity
|
amount := price * quantity
|
||||||
if amount > maxAmount {
|
if amount > maxAmount {
|
||||||
ratio := maxAmount / amount
|
ratio := maxAmount / amount
|
||||||
quantity *= ratio
|
quantity *= ratio
|
||||||
|
|
43
pkg/bbgo/order_processor_test.go
Normal file
43
pkg/bbgo/order_processor_test.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package bbgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdjustQuantityByMinAmount(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
quantity, price, minAmount float64
|
||||||
|
}
|
||||||
|
type testcase struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wanted float64
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testcase{
|
||||||
|
{
|
||||||
|
name: "amount too small",
|
||||||
|
args: args{0.1, 10.0, 10.0},
|
||||||
|
wanted: 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "amount equals to min amount",
|
||||||
|
args: args{1.0, 10.0, 10.0},
|
||||||
|
wanted: 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "amount is greater than min amount",
|
||||||
|
args: args{2.0, 10.0, 10.0},
|
||||||
|
wanted: 2.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
q := adjustQuantityByMinAmount(test.args.quantity, test.args.price, test.args.minAmount)
|
||||||
|
assert.Equal(t, test.wanted, q)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,49 +6,40 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SymbolBasedOrderExecutor struct {
|
type SymbolBasedRiskController struct {
|
||||||
BasicRiskControlOrderExecutor *BasicRiskControlOrderExecutor `json:"basic,omitempty" yaml:"basic,omitempty"`
|
BasicRiskController *BasicRiskController `json:"basic,omitempty" yaml:"basic,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RiskControlOrderExecutors struct {
|
type RiskControlOrderExecutor struct {
|
||||||
*ExchangeOrderExecutor
|
*ExchangeOrderExecutor
|
||||||
|
|
||||||
// Symbol => Executor config
|
// Symbol => Executor config
|
||||||
BySymbol map[string]*SymbolBasedOrderExecutor `json:"bySymbol,omitempty" yaml:"bySymbol,omitempty"`
|
BySymbol map[string]*SymbolBasedRiskController `json:"bySymbol,omitempty" yaml:"bySymbol,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RiskControlOrderExecutors) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (types.OrderSlice, error) {
|
func (e *RiskControlOrderExecutor) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (retOrders types.OrderSlice, err error) {
|
||||||
var symbolOrders = make(map[string][]types.SubmitOrder, len(orders))
|
var symbolOrders = groupSubmitOrdersBySymbol(orders)
|
||||||
for _, order := range orders {
|
|
||||||
symbolOrders[order.Symbol] = append(symbolOrders[order.Symbol], order)
|
|
||||||
}
|
|
||||||
|
|
||||||
var retOrders []types.Order
|
|
||||||
|
|
||||||
for symbol, orders := range symbolOrders {
|
for symbol, orders := range symbolOrders {
|
||||||
var err error
|
if controller, ok := e.BySymbol[symbol]; ok && controller != nil {
|
||||||
var retOrders2 []types.Order
|
orders, err = controller.BasicRiskController.ProcessOrders(e.session, orders...)
|
||||||
if exec, ok := e.BySymbol[symbol]; ok && exec.BasicRiskControlOrderExecutor != nil {
|
|
||||||
retOrders2, err = exec.BasicRiskControlOrderExecutor.SubmitOrders(ctx, orders...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return retOrders, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
|
||||||
retOrders2, err = e.ExchangeOrderExecutor.SubmitOrders(ctx, orders...)
|
|
||||||
if err != nil {
|
|
||||||
return retOrders, err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retOrders2, err := e.ExchangeOrderExecutor.SubmitOrders(ctx, orders...)
|
||||||
|
if err != nil {
|
||||||
|
return retOrders, err
|
||||||
|
}
|
||||||
|
|
||||||
retOrders = append(retOrders, retOrders2...)
|
retOrders = append(retOrders, retOrders2...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return retOrders, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionBasedRiskControl struct {
|
type SessionBasedRiskControl struct {
|
||||||
OrderExecutor *RiskControlOrderExecutors `json:"orderExecutors,omitempty" yaml:"orderExecutors"`
|
OrderExecutor *RiskControlOrderExecutor `json:"orderExecutor,omitempty" yaml:"orderExecutor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (control *SessionBasedRiskControl) SetBaseOrderExecutor(executor *ExchangeOrderExecutor) {
|
func (control *SessionBasedRiskControl) SetBaseOrderExecutor(executor *ExchangeOrderExecutor) {
|
||||||
|
@ -57,16 +48,15 @@ func (control *SessionBasedRiskControl) SetBaseOrderExecutor(executor *ExchangeO
|
||||||
}
|
}
|
||||||
|
|
||||||
control.OrderExecutor.ExchangeOrderExecutor = executor
|
control.OrderExecutor.ExchangeOrderExecutor = executor
|
||||||
|
}
|
||||||
|
|
||||||
if control.OrderExecutor.BySymbol == nil {
|
func groupSubmitOrdersBySymbol(orders []types.SubmitOrder) map[string][]types.SubmitOrder {
|
||||||
return
|
var symbolOrders = make(map[string][]types.SubmitOrder, len(orders))
|
||||||
|
for _, order := range orders {
|
||||||
|
symbolOrders[order.Symbol] = append(symbolOrders[order.Symbol], order)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, exec := range control.OrderExecutor.BySymbol {
|
return symbolOrders
|
||||||
if exec.BasicRiskControlOrderExecutor != nil {
|
|
||||||
exec.BasicRiskControlOrderExecutor.ExchangeOrderExecutor = executor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RiskControls struct {
|
type RiskControls struct {
|
||||||
|
|
2
pkg/bbgo/testdata/order_executor.yaml
vendored
2
pkg/bbgo/testdata/order_executor.yaml
vendored
|
@ -16,7 +16,7 @@ riskControls:
|
||||||
sessionBased:
|
sessionBased:
|
||||||
# max is the session name that you want to configure the risk control
|
# max is the session name that you want to configure the risk control
|
||||||
max:
|
max:
|
||||||
orderExecutors:
|
orderExecutor:
|
||||||
bySymbol:
|
bySymbol:
|
||||||
BTCUSDT:
|
BTCUSDT:
|
||||||
basic:
|
basic:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user