2023-06-29 07:06:03 +00:00
|
|
|
package riskcontrol
|
|
|
|
|
|
|
|
import (
|
2023-08-31 06:50:50 +00:00
|
|
|
"time"
|
|
|
|
|
2023-07-03 09:09:13 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
2023-06-29 07:06:03 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
2023-08-31 06:50:50 +00:00
|
|
|
indicatorv2 "github.com/c9s/bbgo/pkg/indicator/v2"
|
2023-06-29 07:06:03 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
type CircuitBreakRiskControl struct {
|
|
|
|
// Since price could be fluctuated large,
|
|
|
|
// use an EWMA to smooth it in running time
|
2023-08-31 06:50:50 +00:00
|
|
|
price *indicatorv2.EWMAStream
|
|
|
|
position *types.Position
|
|
|
|
profitStats *types.ProfitStats
|
|
|
|
lossThreshold fixedpoint.Value
|
|
|
|
haltedDuration time.Duration
|
|
|
|
|
|
|
|
haltedAt time.Time
|
2023-06-29 07:06:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewCircuitBreakRiskControl(
|
|
|
|
position *types.Position,
|
2023-07-10 08:54:22 +00:00
|
|
|
price *indicatorv2.EWMAStream,
|
2023-07-03 09:09:13 +00:00
|
|
|
lossThreshold fixedpoint.Value,
|
2023-08-31 06:50:50 +00:00
|
|
|
profitStats *types.ProfitStats,
|
|
|
|
haltedDuration time.Duration,
|
|
|
|
) *CircuitBreakRiskControl {
|
2023-06-29 07:06:03 +00:00
|
|
|
return &CircuitBreakRiskControl{
|
2023-08-31 06:50:50 +00:00
|
|
|
price: price,
|
|
|
|
position: position,
|
|
|
|
profitStats: profitStats,
|
|
|
|
lossThreshold: lossThreshold,
|
|
|
|
haltedDuration: haltedDuration,
|
2023-06-29 07:06:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-31 06:50:50 +00:00
|
|
|
func (c *CircuitBreakRiskControl) IsOverHaltedDuration() bool {
|
|
|
|
return time.Since(c.haltedAt) >= c.haltedDuration
|
|
|
|
}
|
|
|
|
|
2023-06-29 07:06:03 +00:00
|
|
|
// IsHalted returns whether we reached the circuit break condition set for this day?
|
2023-08-31 06:50:50 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-06-29 07:06:03 +00:00
|
|
|
var unrealized = c.position.UnrealizedProfit(fixedpoint.NewFromFloat(c.price.Last(0)))
|
2023-07-03 09:09:13 +00:00
|
|
|
log.Infof("[CircuitBreakRiskControl] realized PnL = %f, unrealized PnL = %f\n",
|
|
|
|
c.profitStats.TodayPnL.Float64(),
|
|
|
|
unrealized.Float64())
|
2023-08-31 06:50:50 +00:00
|
|
|
|
2023-09-21 06:57:55 +00:00
|
|
|
isHalted := unrealized.Add(c.profitStats.TodayPnL).Compare(c.lossThreshold) <= 0
|
|
|
|
if isHalted {
|
2023-08-31 06:50:50 +00:00
|
|
|
c.haltedAt = t
|
|
|
|
}
|
|
|
|
|
2023-09-21 06:57:55 +00:00
|
|
|
return isHalted
|
2023-06-29 07:06:03 +00:00
|
|
|
}
|