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,
|
s.Position,
|
||||||
session.Indicators(s.Symbol).EWMA(s.CircuitBreakEMA),
|
session.Indicators(s.Symbol).EWMA(s.CircuitBreakEMA),
|
||||||
s.CircuitBreakLossThreshold,
|
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.
|
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:
|
Check for circuit break before submitting orders:
|
||||||
```
|
```
|
||||||
// Circuit break when accumulated losses are over break condition
|
// Circuit break when accumulated losses are over break condition
|
||||||
if s.circuitBreakRiskControl.IsHalted() {
|
if s.circuitBreakRiskControl.IsHalted(kline.EndTime) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,68 @@
|
||||||
package riskcontrol
|
package riskcontrol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"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"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CircuitBreakRiskControl struct {
|
type CircuitBreakRiskControl struct {
|
||||||
// Since price could be fluctuated large,
|
// Since price could be fluctuated large,
|
||||||
// use an EWMA to smooth it in running time
|
// use an EWMA to smooth it in running time
|
||||||
price *indicatorv2.EWMAStream
|
price *indicatorv2.EWMAStream
|
||||||
position *types.Position
|
position *types.Position
|
||||||
profitStats *types.ProfitStats
|
profitStats *types.ProfitStats
|
||||||
lossThreshold fixedpoint.Value
|
lossThreshold fixedpoint.Value
|
||||||
|
haltedDuration time.Duration
|
||||||
|
|
||||||
|
isHalted bool
|
||||||
|
haltedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCircuitBreakRiskControl(
|
func NewCircuitBreakRiskControl(
|
||||||
position *types.Position,
|
position *types.Position,
|
||||||
price *indicatorv2.EWMAStream,
|
price *indicatorv2.EWMAStream,
|
||||||
lossThreshold fixedpoint.Value,
|
lossThreshold fixedpoint.Value,
|
||||||
profitStats *types.ProfitStats) *CircuitBreakRiskControl {
|
profitStats *types.ProfitStats,
|
||||||
|
haltedDuration time.Duration,
|
||||||
|
) *CircuitBreakRiskControl {
|
||||||
return &CircuitBreakRiskControl{
|
return &CircuitBreakRiskControl{
|
||||||
price: price,
|
price: price,
|
||||||
position: position,
|
position: position,
|
||||||
profitStats: profitStats,
|
profitStats: profitStats,
|
||||||
lossThreshold: lossThreshold,
|
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?
|
// 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)))
|
var unrealized = c.position.UnrealizedProfit(fixedpoint.NewFromFloat(c.price.Last(0)))
|
||||||
log.Infof("[CircuitBreakRiskControl] realized PnL = %f, unrealized PnL = %f\n",
|
log.Infof("[CircuitBreakRiskControl] realized PnL = %f, unrealized PnL = %f\n",
|
||||||
c.profitStats.TodayPnL.Float64(),
|
c.profitStats.TodayPnL.Float64(),
|
||||||
unrealized.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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
@ -68,11 +69,13 @@ func Test_IsHalted(t *testing.T) {
|
||||||
},
|
},
|
||||||
priceEWMA,
|
priceEWMA,
|
||||||
breakCondition,
|
breakCondition,
|
||||||
&types.ProfitStats{
|
&types.ProfitStats{},
|
||||||
TodayPnL: realizedPnL,
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
@ -83,6 +84,7 @@ func (s *Strategy) Initialize(ctx context.Context, environ *bbgo.Environment, se
|
||||||
s.Position,
|
s.Position,
|
||||||
session.Indicators(market.Symbol).EWMA(s.CircuitBreakEMA),
|
session.Indicators(market.Symbol).EWMA(s.CircuitBreakEMA),
|
||||||
s.CircuitBreakLossThreshold,
|
s.CircuitBreakLossThreshold,
|
||||||
s.ProfitStats)
|
s.ProfitStats,
|
||||||
|
24*time.Hour)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
@ -123,7 +124,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.Position,
|
s.Position,
|
||||||
session.Indicators(s.Symbol).EWMA(s.CircuitBreakEMA),
|
session.Indicators(s.Symbol).EWMA(s.CircuitBreakEMA),
|
||||||
s.CircuitBreakLossThreshold,
|
s.CircuitBreakLossThreshold,
|
||||||
s.ProfitStats)
|
s.ProfitStats,
|
||||||
|
24*time.Hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
scale, err := s.LiquiditySlideRule.Scale()
|
scale, err := s.LiquiditySlideRule.Scale()
|
||||||
|
@ -275,21 +277,21 @@ func (s *Strategy) placeAdjustmentOrders(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) placeLiquidityOrders(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")
|
log.Warn("circuitBreakRiskControl: trading halted")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.liquidityOrderBook.GracefulCancel(ctx, s.Session.Exchange)
|
err = s.liquidityOrderBook.GracefulCancel(ctx, s.Session.Exchange)
|
||||||
if logErr(err, "unable to cancel orders") {
|
if logErr(err, "unable to cancel orders") {
|
||||||
return
|
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() {
|
if ticker.Buy.IsZero() && ticker.Sell.IsZero() {
|
||||||
ticker.Sell = ticker.Last.Add(s.Market.TickSize)
|
ticker.Sell = ticker.Last.Add(s.Market.TickSize)
|
||||||
ticker.Buy = ticker.Last.Sub(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)
|
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
|
// IsOver24Hours checks if the since time is over 24 hours
|
||||||
func (s *ProfitStats) IsOver24Hours() bool {
|
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) {
|
func (s *ProfitStats) ResetToday(t time.Time) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user