mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Merge pull request #1327 from c9s/narumi/fix-position-risk
FIX: Fix duplicate orders caused by position risk control
This commit is contained in:
commit
2f65793522
|
@ -1,10 +1,27 @@
|
||||||
---
|
---
|
||||||
|
backtest:
|
||||||
|
startTime: "2023-01-01"
|
||||||
|
endTime: "2023-05-31"
|
||||||
|
symbols:
|
||||||
|
- USDCUSDT
|
||||||
|
sessions:
|
||||||
|
- max
|
||||||
|
accounts:
|
||||||
|
max:
|
||||||
|
balances:
|
||||||
|
USDC: 500.0
|
||||||
|
USDT: 500.0
|
||||||
|
|
||||||
exchangeStrategies:
|
exchangeStrategies:
|
||||||
- on: max
|
- on: max
|
||||||
fixedmaker:
|
fixedmaker:
|
||||||
symbol: BTCUSDT
|
symbol: USDCUSDT
|
||||||
interval: 5m
|
interval: 5m
|
||||||
halfSpread: 0.05%
|
halfSpread: 0.05%
|
||||||
quantity: 0.005
|
quantity: 15
|
||||||
orderType: LIMIT_MAKER
|
orderType: LIMIT_MAKER
|
||||||
dryRun: true
|
dryRun: false
|
||||||
|
|
||||||
|
positionHardLimit: 1500
|
||||||
|
maxPositionQuantity: 1500
|
||||||
|
circuitBreakLossThreshold: -0.15
|
||||||
|
|
|
@ -24,6 +24,11 @@ type PositionRiskControl struct {
|
||||||
// only used in the ModifiedQuantity method
|
// only used in the ModifiedQuantity method
|
||||||
sliceQuantity fixedpoint.Value
|
sliceQuantity fixedpoint.Value
|
||||||
|
|
||||||
|
// activeOrderBook is used to store orders created by the risk control.
|
||||||
|
// This allows us to cancel them before submitting the position release
|
||||||
|
// orders, preventing duplicate orders.
|
||||||
|
activeOrderBook *bbgo.ActiveOrderBook
|
||||||
|
|
||||||
releasePositionCallbacks []func(quantity fixedpoint.Value, side types.SideType)
|
releasePositionCallbacks []func(quantity fixedpoint.Value, side types.SideType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,26 +39,6 @@ func NewPositionRiskControl(orderExecutor bbgo.OrderExecutorExtended, hardLimit,
|
||||||
sliceQuantity: quantity,
|
sliceQuantity: quantity,
|
||||||
}
|
}
|
||||||
|
|
||||||
control.OnReleasePosition(func(quantity fixedpoint.Value, side types.SideType) {
|
|
||||||
pos := orderExecutor.Position()
|
|
||||||
submitOrder := types.SubmitOrder{
|
|
||||||
Symbol: pos.Symbol,
|
|
||||||
Market: pos.Market,
|
|
||||||
Side: side,
|
|
||||||
Type: types.OrderTypeMarket,
|
|
||||||
Quantity: quantity,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("RiskControl: position limit exceeded, submitting order to reduce position: %+v", submitOrder)
|
|
||||||
createdOrders, err := orderExecutor.SubmitOrders(context.Background(), submitOrder)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Errorf("failed to submit orders")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("created position release orders: %+v", createdOrders)
|
|
||||||
})
|
|
||||||
|
|
||||||
// register position update handler: check if position is over the hard limit
|
// register position update handler: check if position is over the hard limit
|
||||||
orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
if fixedpoint.Compare(position.Base, hardLimit) > 0 {
|
if fixedpoint.Compare(position.Base, hardLimit) > 0 {
|
||||||
|
@ -68,6 +53,37 @@ func NewPositionRiskControl(orderExecutor bbgo.OrderExecutorExtended, hardLimit,
|
||||||
return control
|
return control
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PositionRiskControl) Initialize(ctx context.Context, session *bbgo.ExchangeSession) {
|
||||||
|
p.activeOrderBook = bbgo.NewActiveOrderBook("")
|
||||||
|
p.activeOrderBook.BindStream(session.UserDataStream)
|
||||||
|
|
||||||
|
p.OnReleasePosition(func(quantity fixedpoint.Value, side types.SideType) {
|
||||||
|
if err := p.activeOrderBook.GracefulCancel(ctx, session.Exchange); err != nil {
|
||||||
|
log.WithError(err).Errorf("failed to cancel orders")
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := p.orderExecutor.Position()
|
||||||
|
submitOrder := types.SubmitOrder{
|
||||||
|
Symbol: pos.Symbol,
|
||||||
|
Market: pos.Market,
|
||||||
|
Side: side,
|
||||||
|
Type: types.OrderTypeMarket,
|
||||||
|
Quantity: quantity,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("RiskControl: position limit exceeded, submitting order to reduce position: %+v", submitOrder)
|
||||||
|
createdOrders, err := p.orderExecutor.SubmitOrders(ctx, submitOrder)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("failed to submit orders")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("created position release orders: %+v", createdOrders)
|
||||||
|
|
||||||
|
p.activeOrderBook.Add(createdOrders...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ModifiedQuantity returns sliceQuantity controlled by position risks
|
// ModifiedQuantity returns sliceQuantity controlled by position risks
|
||||||
// For buy orders, modify sliceQuantity = min(hardLimit - position, sliceQuantity), limiting by positive position
|
// For buy orders, modify sliceQuantity = min(hardLimit - position, sliceQuantity), limiting by positive position
|
||||||
// For sell orders, modify sliceQuantity = min(hardLimit - (-position), sliceQuantity), limiting by negative position
|
// For sell orders, modify sliceQuantity = min(hardLimit - (-position), sliceQuantity), limiting by negative position
|
||||||
|
|
|
@ -76,6 +76,7 @@ func (s *Strategy) Initialize(ctx context.Context, environ *bbgo.Environment, se
|
||||||
if !s.PositionHardLimit.IsZero() && !s.MaxPositionQuantity.IsZero() {
|
if !s.PositionHardLimit.IsZero() && !s.MaxPositionQuantity.IsZero() {
|
||||||
log.Infof("positionHardLimit and maxPositionQuantity are configured, setting up PositionRiskControl...")
|
log.Infof("positionHardLimit and maxPositionQuantity are configured, setting up PositionRiskControl...")
|
||||||
s.positionRiskControl = riskcontrol.NewPositionRiskControl(s.OrderExecutor, s.PositionHardLimit, s.MaxPositionQuantity)
|
s.positionRiskControl = riskcontrol.NewPositionRiskControl(s.OrderExecutor, s.PositionHardLimit, s.MaxPositionQuantity)
|
||||||
|
s.positionRiskControl.Initialize(ctx, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.CircuitBreakLossThreshold.IsZero() {
|
if !s.CircuitBreakLossThreshold.IsZero() {
|
||||||
|
|
|
@ -63,7 +63,7 @@ func (s *Strategy) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.HalfSpread.Float64() <= 0 {
|
if s.HalfSpread.Float64() <= 0 {
|
||||||
return fmt.Errorf("halfSpreadRatio should be positive")
|
return fmt.Errorf("halfSpread should be positive")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user