refactor and configure risk control order executor

This commit is contained in:
c9s 2020-10-26 21:36:47 +08:00
parent 59aa5c5ee2
commit c324a791f6
8 changed files with 151 additions and 43 deletions

View File

@ -28,6 +28,23 @@ sessions:
exchange: binance
envVarPrefix: binance
riskControls:
# This is the session-based risk controller, which let you configure different risk controller by session.
sessionBased:
# "max" is the session name that you want to configure the risk control
max:
# orderExecutors is one of the risk control
orderExecutors:
# symbol-routed order executor
bySymbol:
BTCUSDT:
# basic risk control order executor
basic:
minQuoteBalance: 1000.0
maxBaseAssetBalance: 2.0
minBaseAssetBalance: 0.1
maxOrderAmount: 100.0
exchangeStrategies:
- on: binance
buyandhold:

View File

@ -50,18 +50,17 @@ func (e *ExchangeOrderExecutor) SubmitOrders(ctx context.Context, orders ...type
return e.session.Exchange.SubmitOrders(ctx, formattedOrders...)
}
type RiskControlOrderExecutor struct {
type BasicRiskControlOrderExecutor struct {
Notifiability `json:"-"`
session *ExchangeSession
MinQuoteBalance fixedpoint.Value `json:"minQuoteBalance,omitempty"`
MaxAssetBalance fixedpoint.Value `json:"maxBaseAssetBalance,omitempty"`
MinAssetBalance fixedpoint.Value `json:"minBaseAssetBalance,omitempty"`
MaxOrderAmount fixedpoint.Value `json:"maxOrderAmount,omitempty"`
session *ExchangeSession
}
func (e *RiskControlOrderExecutor) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) ([]types.Order, error) {
func (e *BasicRiskControlOrderExecutor) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) ([]types.Order, error) {
var formattedOrders []types.SubmitOrder
for _, order := range orders {
currentPrice, ok := e.session.lastPrices[order.Symbol]
@ -86,7 +85,7 @@ func (e *RiskControlOrderExecutor) SubmitOrders(ctx context.Context, orders ...t
if baseBalance, ok := balances[market.BaseCurrency]; ok {
if e.MaxAssetBalance > 0 && baseBalance.Available > e.MaxAssetBalance.Float64() {
return nil, errors.Wrapf(ErrAssetBalanceLevelTooHigh, "asset balance level is too high: %f > %f", baseBalance.Available, e.MaxAssetBalance)
return nil, errors.Wrapf(ErrAssetBalanceLevelTooHigh, "asset balance level is too high: %f > %f", baseBalance.Available, e.MaxAssetBalance.Float64())
}
}
@ -107,7 +106,7 @@ func (e *RiskControlOrderExecutor) SubmitOrders(ctx context.Context, orders ...t
if balance, ok := balances[market.BaseCurrency]; ok {
if e.MinAssetBalance > 0 && balance.Available < e.MinAssetBalance.Float64() {
return nil, errors.Wrapf(ErrAssetBalanceLevelTooLow, "asset balance level is too low: %f > %f", balance.Available, e.MinAssetBalance)
return nil, errors.Wrapf(ErrAssetBalanceLevelTooLow, "asset balance level is too low: %f > %f", balance.Available, e.MinAssetBalance.Float64())
}
quantity = adjustQuantityByMinAmount(quantity, currentPrice, market.MinNotional*1.01)
@ -134,7 +133,7 @@ func (e *RiskControlOrderExecutor) SubmitOrders(ctx context.Context, orders ...t
}
}
// udpate quantity and format the order
// update quantity and format the order
order.Quantity = quantity
o, err := formatOrder(order, e.session)
if err != nil {

View File

@ -1,11 +0,0 @@
package bbgo
import (
"testing"
)
func TestOrderProcessor(t *testing.T) {
processor := &OrderProcessor{}
_ = processor
}

79
pkg/bbgo/risk_controls.go Normal file
View File

@ -0,0 +1,79 @@
package bbgo
import (
"context"
"github.com/c9s/bbgo/pkg/types"
)
type SymbolBasedOrderExecutor struct {
BasicRiskControlOrderExecutor *BasicRiskControlOrderExecutor `json:"basic,omitempty" yaml:"basic,omitempty"`
}
type RiskControlOrderExecutors struct {
Notifiability `json:"-"`
session *ExchangeSession
// Symbol => Executor config
BySymbol map[string]*SymbolBasedOrderExecutor `json:"bySymbol,omitempty" yaml:"bySymbol,omitempty"`
}
func (e *RiskControlOrderExecutors) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) ([]types.Order, error) {
var symbolOrders = make(map[string][]types.SubmitOrder, len(orders))
for _, order := range orders {
symbolOrders[order.Symbol] = append(symbolOrders[order.Symbol], order)
}
var retOrders []types.Order
for symbol, orders := range symbolOrders {
if exec, ok := e.BySymbol[symbol]; ok && exec.BasicRiskControlOrderExecutor != nil {
retOrders2, err := exec.BasicRiskControlOrderExecutor.SubmitOrders(ctx, orders...)
if err != nil {
return retOrders, err
}
retOrders = append(retOrders, retOrders2...)
}
}
return retOrders, nil
}
type SessionBasedRiskControl struct {
OrderExecutor *RiskControlOrderExecutors `json:"orderExecutors,omitempty" yaml:"orderExecutors"`
}
func (control *SessionBasedRiskControl) SetSession(session *ExchangeSession) {
if control.OrderExecutor == nil {
return
}
control.OrderExecutor.session = session
if control.OrderExecutor.BySymbol == nil {
return
}
for _, exec := range control.OrderExecutor.BySymbol {
if exec.BasicRiskControlOrderExecutor != nil {
exec.BasicRiskControlOrderExecutor.session = session
}
}
}
type RiskControls struct {
SessionBasedRiskControl map[string]*SessionBasedRiskControl `json:"sessionBased,omitempty" yaml:"sessionBased,omitempty"`
}
func (controls *RiskControls) SetSession(name string, session *ExchangeSession) {
if controls.SessionBasedRiskControl == nil {
return
}
control, ok := controls.SessionBasedRiskControl[name]
if !ok {
return
}
control.SetSession(session)
}

View File

@ -35,6 +35,8 @@ type Trader struct {
environment *Environment
riskControls *RiskControls
crossExchangeStrategies []CrossExchangeStrategy
exchangeStrategies map[string][]SingleExchangeStrategy
@ -70,6 +72,10 @@ func (trader *Trader) AttachCrossExchangeStrategy(strategy CrossExchangeStrategy
return trader
}
func (trader *Trader) SetRiskControls(riskControls *RiskControls) {
trader.riskControls = riskControls
}
func (trader *Trader) Run(ctx context.Context) error {
if err := trader.environment.Init(ctx); err != nil {
return err
@ -78,13 +84,26 @@ func (trader *Trader) Run(ctx context.Context) error {
// load and run session strategies
for sessionName, strategies := range trader.exchangeStrategies {
session := trader.environment.sessions[sessionName]
// we can move this to the exchange session,
// We can move this to the exchange session,
// that way we can mount the notification on the exchange with DSL
orderExecutor := &ExchangeOrderExecutor{
// This is the default order executor
var orderExecutor OrderExecutor = &ExchangeOrderExecutor{
Notifiability: trader.Notifiability,
session: session,
}
// Since the risk controls are loaded from the config file
if trader.riskControls != nil && trader.riskControls.SessionBasedRiskControl != nil {
trader.riskControls.SetSession(sessionName, session)
if control, ok := trader.riskControls.SessionBasedRiskControl[sessionName]; ok {
if control.OrderExecutor != nil {
orderExecutor = control.OrderExecutor
}
}
}
for _, strategy := range strategies {
err := strategy.Run(ctx, orderExecutor, session)
if err != nil {

View File

@ -22,19 +22,9 @@ type PnLReporter struct {
When StringSlice `json:"when" yaml:"when"`
}
type SymbolBasedOrderExecutor struct {
RiskControlOrderExecutor *bbgo.RiskControlOrderExecutor `json:"RiskControlOrderExecutor,omitempty"`
}
type OrderExecutor struct {
// Symbol => Executor config
BySymbol map[string]*SymbolBasedOrderExecutor `json:"bySymbol,omitempty" yaml:"bySymbol,omitempty"`
}
type Session struct {
ExchangeName string `json:"exchange" yaml:"exchange"`
EnvVarPrefix string `json:"envVarPrefix" yaml:"envVarPrefix"`
OrderExecutorConfig *OrderExecutor `json:"orderExecutor,omitempty" yaml:"orderExecutor"`
ExchangeName string `json:"exchange" yaml:"exchange"`
EnvVarPrefix string `json:"envVarPrefix" yaml:"envVarPrefix"`
}
type Config struct {
@ -42,6 +32,8 @@ type Config struct {
Sessions map[string]Session `json:"sessions,omitempty" yaml:"sessions,omitempty"`
RiskControls *bbgo.RiskControls `json:"riskControls,omitempty" yaml:"riskControls,omitempty"`
ExchangeStrategies []SingleExchangeStrategyConfig
CrossExchangeStrategies []bbgo.CrossExchangeStrategy

View File

@ -39,10 +39,17 @@ func TestLoadConfig(t *testing.T) {
assert.True(t, ok)
assert.NotNil(t, session)
assert.NotNil(t, session.OrderExecutorConfig)
assert.NotNil(t, session.OrderExecutorConfig.BySymbol)
riskControls := config.RiskControls
assert.NotNil(t, riskControls)
assert.NotNil(t, riskControls.SessionBasedRiskControl)
executorConf, ok := session.OrderExecutorConfig.BySymbol["BTCUSDT"]
conf, ok := riskControls.SessionBasedRiskControl["max"]
assert.True(t, ok)
assert.NotNil(t, conf)
assert.NotNil(t, conf.OrderExecutor)
assert.NotNil(t, conf.OrderExecutor.BySymbol)
executorConf, ok := conf.OrderExecutor.BySymbol["BTCUSDT"]
assert.True(t, ok)
assert.NotNil(t, executorConf)
},

View File

@ -6,16 +6,22 @@ sessions:
max:
exchange: max
envVarPrefix: max
orderExecutor:
bySymbol:
BTCUSDT:
RiskControlOrderExecutor:
minQuoteBalance: 1000.0
maxBaseAssetBalance: 10.0
minBaseAssetBalance: 1.0
maxOrderAmount: 100.0
binance:
exchange: binance
envVarPrefix: binance
riskControls:
# session-based risk controller
sessionBased:
# max is the session name that you want to configure the risk control
max:
orderExecutors:
bySymbol:
BTCUSDT:
basic:
minQuoteBalance: 1000.0
maxBaseAssetBalance: 10.0
minBaseAssetBalance: 1.0
maxOrderAmount: 100.0