riskcontrol: move release position order submission into the pos risk control

This commit is contained in:
c9s 2023-07-04 21:31:47 +08:00
parent 0426c18757
commit c8ae36ddfc
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
4 changed files with 87 additions and 50 deletions

View File

@ -62,7 +62,7 @@ func NewGeneralOrderExecutor(session *ExchangeSession, symbol, strategy, strateg
tradeCollector: NewTradeCollector(symbol, position, orderStore),
}
if session.Margin {
if session != nil && session.Margin {
executor.startMarginAssetUpdater(context.Background())
}

View File

@ -1,6 +1,8 @@
package riskcontrol
import (
"context"
log "github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
@ -10,36 +12,71 @@ import (
//go:generate callbackgen -type PositionRiskControl
type PositionRiskControl struct {
orderExecutor *bbgo.GeneralOrderExecutor
// hardLimit is the maximum base position you can hold
hardLimit fixedpoint.Value
quantity fixedpoint.Value
// sliceQuantity is the maximum quantity of the order you want to place.
// only used in the ModifiedQuantity method
sliceQuantity fixedpoint.Value
releasePositionCallbacks []func(quantity fixedpoint.Value, side types.SideType)
}
func NewPositionRiskControl(hardLimit, quantity fixedpoint.Value, tradeCollector *bbgo.TradeCollector) *PositionRiskControl {
p := &PositionRiskControl{
hardLimit: hardLimit,
quantity: quantity,
func NewPositionRiskControl(hardLimit, quantity fixedpoint.Value, orderExecutor *bbgo.GeneralOrderExecutor) *PositionRiskControl {
control := &PositionRiskControl{
orderExecutor: orderExecutor,
hardLimit: hardLimit,
sliceQuantity: quantity,
}
control.OnReleasePosition(func(quantity fixedpoint.Value, side types.SideType) {
pos := orderExecutor.Position()
createdOrders, err := orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{
Symbol: pos.Symbol,
Market: pos.Market,
Side: side,
Type: types.OrderTypeMarket,
Quantity: quantity,
})
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
tradeCollector.OnPositionUpdate(func(position *types.Position) {
orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
if fixedpoint.Compare(position.Base, hardLimit) > 0 {
log.Infof("position %f is over hardlimit %f, releasing position...", position.Base.Float64(), hardLimit.Float64())
p.EmitReleasePosition(position.Base.Sub(hardLimit), types.SideTypeSell)
control.EmitReleasePosition(position.Base.Sub(hardLimit), types.SideTypeSell)
} else if fixedpoint.Compare(position.Base, hardLimit.Neg()) < 0 {
log.Infof("position %f is over hardlimit %f, releasing position...", position.Base.Float64(), hardLimit.Float64())
p.EmitReleasePosition(position.Base.Neg().Sub(hardLimit), types.SideTypeBuy)
control.EmitReleasePosition(position.Base.Neg().Sub(hardLimit), types.SideTypeBuy)
}
})
return p
return control
}
// ModifiedQuantity returns quantity controlled by position risks
// For buy orders, mod quantity = min(hardLimit - position, quantity), limiting by positive position
// For sell orders, mod quantity = min(hardLimit - (-position), quantity), limiting by negative position
// ModifiedQuantity returns sliceQuantity controlled by position risks
// 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
//
// Pass the current base position to this method, and it returns the maximum sliceQuantity for placing the orders.
// This works for both Long/Short position
func (p *PositionRiskControl) ModifiedQuantity(position fixedpoint.Value) (buyQuantity, sellQuantity fixedpoint.Value) {
return fixedpoint.Min(p.hardLimit.Sub(position), p.quantity),
fixedpoint.Min(p.hardLimit.Add(position), p.quantity)
if p.sliceQuantity.IsZero() {
buyQuantity = p.hardLimit.Sub(position)
sellQuantity = p.hardLimit.Add(position)
return buyQuantity, sellQuantity
}
buyQuantity = fixedpoint.Min(p.hardLimit.Sub(position), p.sliceQuantity)
sellQuantity = fixedpoint.Min(p.hardLimit.Add(position), p.sliceQuantity)
return buyQuantity, sellQuantity
}

View File

@ -11,8 +11,17 @@ import (
)
func Test_ModifiedQuantity(t *testing.T) {
riskControl := NewPositionRiskControl(fixedpoint.NewFromInt(10), fixedpoint.NewFromInt(2), &bbgo.TradeCollector{})
pos := &types.Position{
Market: types.Market{
Symbol: "BTCUSDT",
PricePrecision: 8,
VolumePrecision: 8,
QuoteCurrency: "USDT",
BaseCurrency: "BTC",
},
}
orderExecutor := bbgo.NewGeneralOrderExecutor(nil, "BTCUSDT", "strategy", "strategy-1", pos)
riskControl := NewPositionRiskControl(fixedpoint.NewFromInt(10), fixedpoint.NewFromInt(2), orderExecutor)
cases := []struct {
name string
@ -43,19 +52,6 @@ func Test_ModifiedQuantity(t *testing.T) {
}
func TestReleasePositionCallbacks(t *testing.T) {
var position fixedpoint.Value
tradeCollector := &bbgo.TradeCollector{}
riskControl := NewPositionRiskControl(fixedpoint.NewFromInt(10), fixedpoint.NewFromInt(2), tradeCollector)
riskControl.OnReleasePosition(func(quantity fixedpoint.Value, side types.SideType) {
if side == types.SideTypeBuy {
position = position.Add(quantity)
} else {
position = position.Sub(quantity)
}
})
cases := []struct {
name string
position fixedpoint.Value
@ -84,9 +80,29 @@ func TestReleasePositionCallbacks(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
position = tc.position
tradeCollector.EmitPositionUpdate(&types.Position{Base: tc.position})
assert.Equal(t, tc.resultPosition, position)
pos := &types.Position{
Base: tc.position,
Market: types.Market{
Symbol: "BTCUSDT",
PricePrecision: 8,
VolumePrecision: 8,
QuoteCurrency: "USDT",
BaseCurrency: "BTC",
},
}
orderExecutor := bbgo.NewGeneralOrderExecutor(nil, "BTCUSDT", "strategy", "strategy-1", pos)
riskControl := NewPositionRiskControl(fixedpoint.NewFromInt(10), fixedpoint.NewFromInt(2), orderExecutor)
riskControl.OnReleasePosition(func(quantity fixedpoint.Value, side types.SideType) {
if side == types.SideTypeBuy {
pos.Base = pos.Base.Add(quantity)
} else {
pos.Base = pos.Base.Sub(quantity)
}
})
orderExecutor.TradeCollector().EmitPositionUpdate(&types.Position{Base: tc.position})
assert.Equal(t, tc.resultPosition, pos.Base)
})
}
}

View File

@ -146,23 +146,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if !s.PositionHardLimit.IsZero() && !s.MaxPositionQuantity.IsZero() {
log.Infof("positionHardLimit and maxPositionQuantity are configured, setting up PositionRiskControl...")
s.positionRiskControl = riskcontrol.NewPositionRiskControl(s.PositionHardLimit, s.MaxPositionQuantity, s.orderExecutor.TradeCollector())
s.positionRiskControl.OnReleasePosition(func(quantity fixedpoint.Value, side types.SideType) {
createdOrders, err := s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: s.Symbol,
Market: s.Market,
Side: side,
Type: types.OrderTypeMarket,
Quantity: quantity,
})
if err != nil {
log.WithError(err).Errorf("failed to submit orders")
return
}
log.Infof("created position release orders: %+v", createdOrders)
})
s.positionRiskControl = riskcontrol.NewPositionRiskControl(s.PositionHardLimit, s.MaxPositionQuantity, s.orderExecutor)
}
if !s.CircuitBreakLossThreshold.IsZero() {