mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
refactor and configure risk control order executor
This commit is contained in:
parent
59aa5c5ee2
commit
c324a791f6
|
@ -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:
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
package bbgo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOrderProcessor(t *testing.T) {
|
||||
processor := &OrderProcessor{}
|
||||
_ = processor
|
||||
|
||||
}
|
79
pkg/bbgo/risk_controls.go
Normal file
79
pkg/bbgo/risk_controls.go
Normal 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)
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
|
|
22
pkg/config/testdata/order_executor.yaml
vendored
22
pkg/config/testdata/order_executor.yaml
vendored
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user