mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
fix: reset profit stats when over given duration in circuit break risk control
This commit is contained in:
parent
52412d9ead
commit
57198cc6b0
|
@ -51,7 +51,8 @@ s.circuitBreakRiskControl = riskcontrol.NewCircuitBreakRiskControl(
|
|||
s.Position,
|
||||
session.Indicators(s.Symbol).EWMA(s.CircuitBreakEMA),
|
||||
s.CircuitBreakLossThreshold,
|
||||
s.ProfitStats)
|
||||
s.ProfitStats,
|
||||
24*time.Hour)
|
||||
```
|
||||
|
||||
Should pass in position and profit states. Also need an price EWMA to calculate unrealized profit.
|
||||
|
@ -71,7 +72,7 @@ Circuit break condition should be non-greater than zero.
|
|||
Check for circuit break before submitting orders:
|
||||
```
|
||||
// Circuit break when accumulated losses are over break condition
|
||||
if s.circuitBreakRiskControl.IsHalted() {
|
||||
if s.circuitBreakRiskControl.IsHalted(kline.EndTime) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package riskcontrol
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/indicator/v2"
|
||||
indicatorv2 "github.com/c9s/bbgo/pkg/indicator/v2"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -15,27 +17,52 @@ type CircuitBreakRiskControl struct {
|
|||
position *types.Position
|
||||
profitStats *types.ProfitStats
|
||||
lossThreshold fixedpoint.Value
|
||||
haltedDuration time.Duration
|
||||
|
||||
isHalted bool
|
||||
haltedAt time.Time
|
||||
}
|
||||
|
||||
func NewCircuitBreakRiskControl(
|
||||
position *types.Position,
|
||||
price *indicatorv2.EWMAStream,
|
||||
lossThreshold fixedpoint.Value,
|
||||
profitStats *types.ProfitStats) *CircuitBreakRiskControl {
|
||||
|
||||
profitStats *types.ProfitStats,
|
||||
haltedDuration time.Duration,
|
||||
) *CircuitBreakRiskControl {
|
||||
return &CircuitBreakRiskControl{
|
||||
price: price,
|
||||
position: position,
|
||||
profitStats: profitStats,
|
||||
lossThreshold: lossThreshold,
|
||||
haltedDuration: haltedDuration,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CircuitBreakRiskControl) IsOverHaltedDuration() bool {
|
||||
return time.Since(c.haltedAt) >= c.haltedDuration
|
||||
}
|
||||
|
||||
// IsHalted returns whether we reached the circuit break condition set for this day?
|
||||
func (c *CircuitBreakRiskControl) IsHalted() bool {
|
||||
func (c *CircuitBreakRiskControl) IsHalted(t time.Time) bool {
|
||||
if c.profitStats.IsOver24Hours() {
|
||||
c.profitStats.ResetToday(t)
|
||||
}
|
||||
|
||||
// if we are not over the halted duration, we don't need to check the condition
|
||||
if !c.IsOverHaltedDuration() {
|
||||
return false
|
||||
}
|
||||
|
||||
var unrealized = c.position.UnrealizedProfit(fixedpoint.NewFromFloat(c.price.Last(0)))
|
||||
log.Infof("[CircuitBreakRiskControl] realized PnL = %f, unrealized PnL = %f\n",
|
||||
c.profitStats.TodayPnL.Float64(),
|
||||
unrealized.Float64())
|
||||
return unrealized.Add(c.profitStats.TodayPnL).Compare(c.lossThreshold) <= 0
|
||||
|
||||
c.isHalted = unrealized.Add(c.profitStats.TodayPnL).Compare(c.lossThreshold) <= 0
|
||||
if c.isHalted {
|
||||
c.haltedAt = t
|
||||
}
|
||||
|
||||
return c.isHalted
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package riskcontrol
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
@ -68,11 +69,13 @@ func Test_IsHalted(t *testing.T) {
|
|||
},
|
||||
priceEWMA,
|
||||
breakCondition,
|
||||
&types.ProfitStats{
|
||||
TodayPnL: realizedPnL,
|
||||
},
|
||||
&types.ProfitStats{},
|
||||
24*time.Hour,
|
||||
)
|
||||
assert.Equal(t, tc.isHalted, riskControl.IsHalted())
|
||||
now := time.Now()
|
||||
riskControl.profitStats.ResetToday(now)
|
||||
riskControl.profitStats.TodayPnL = realizedPnL
|
||||
assert.Equal(t, tc.isHalted, riskControl.IsHalted(now.Add(time.Hour)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package common
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
|
@ -83,6 +84,7 @@ func (s *Strategy) Initialize(ctx context.Context, environ *bbgo.Environment, se
|
|||
s.Position,
|
||||
session.Indicators(market.Symbol).EWMA(s.CircuitBreakEMA),
|
||||
s.CircuitBreakLossThreshold,
|
||||
s.ProfitStats)
|
||||
s.ProfitStats,
|
||||
24*time.Hour)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
|
@ -123,7 +124,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.Position,
|
||||
session.Indicators(s.Symbol).EWMA(s.CircuitBreakEMA),
|
||||
s.CircuitBreakLossThreshold,
|
||||
s.ProfitStats)
|
||||
s.ProfitStats,
|
||||
24*time.Hour)
|
||||
}
|
||||
|
||||
scale, err := s.LiquiditySlideRule.Scale()
|
||||
|
@ -275,21 +277,21 @@ func (s *Strategy) placeAdjustmentOrders(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (s *Strategy) placeLiquidityOrders(ctx context.Context) {
|
||||
if s.circuitBreakRiskControl != nil && s.circuitBreakRiskControl.IsHalted() {
|
||||
ticker, err := s.Session.Exchange.QueryTicker(ctx, s.Symbol)
|
||||
if logErr(err, "unable to query ticker") {
|
||||
return
|
||||
}
|
||||
|
||||
if s.circuitBreakRiskControl != nil && s.circuitBreakRiskControl.IsHalted(ticker.Time) {
|
||||
log.Warn("circuitBreakRiskControl: trading halted")
|
||||
return
|
||||
}
|
||||
|
||||
err := s.liquidityOrderBook.GracefulCancel(ctx, s.Session.Exchange)
|
||||
err = s.liquidityOrderBook.GracefulCancel(ctx, s.Session.Exchange)
|
||||
if logErr(err, "unable to cancel orders") {
|
||||
return
|
||||
}
|
||||
|
||||
ticker, err := s.Session.Exchange.QueryTicker(ctx, s.Symbol)
|
||||
if logErr(err, "unable to query ticker") {
|
||||
return
|
||||
}
|
||||
|
||||
if ticker.Buy.IsZero() && ticker.Sell.IsZero() {
|
||||
ticker.Sell = ticker.Last.Add(s.Market.TickSize)
|
||||
ticker.Buy = ticker.Last.Sub(s.Market.TickSize)
|
||||
|
|
|
@ -240,14 +240,9 @@ func (s *ProfitStats) AddTrade(trade Trade) {
|
|||
s.AccumulatedVolume = s.AccumulatedVolume.Add(trade.Quantity)
|
||||
}
|
||||
|
||||
// IsOver checks if the since time is over given duration
|
||||
func (s *ProfitStats) IsOver(d time.Duration) bool {
|
||||
return time.Since(time.Unix(s.TodaySince, 0)) >= d
|
||||
}
|
||||
|
||||
// IsOver24Hours checks if the since time is over 24 hours
|
||||
func (s *ProfitStats) IsOver24Hours() bool {
|
||||
return s.IsOver(24 * time.Hour)
|
||||
return time.Since(time.Unix(s.TodaySince, 0)) >= 24*time.Hour
|
||||
}
|
||||
|
||||
func (s *ProfitStats) ResetToday(t time.Time) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user