mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 01:01:56 +00:00
riskcontrol: move release position order submission into the pos risk control
This commit is contained in:
parent
0426c18757
commit
c8ae36ddfc
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user