96 lines
3.1 KiB
Go
96 lines
3.1 KiB
Go
|
package common
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"time"
|
||
|
|
||
|
log "github.com/sirupsen/logrus"
|
||
|
|
||
|
"git.qtrade.icu/lychiyu/qbtrade/pkg/fixedpoint"
|
||
|
"git.qtrade.icu/lychiyu/qbtrade/pkg/qbtrade"
|
||
|
"git.qtrade.icu/lychiyu/qbtrade/pkg/risk/riskcontrol"
|
||
|
"git.qtrade.icu/lychiyu/qbtrade/pkg/types"
|
||
|
)
|
||
|
|
||
|
type RiskController struct {
|
||
|
PositionHardLimit fixedpoint.Value `json:"positionHardLimit"`
|
||
|
MaxPositionQuantity fixedpoint.Value `json:"maxPositionQuantity"`
|
||
|
CircuitBreakLossThreshold fixedpoint.Value `json:"circuitBreakLossThreshold"`
|
||
|
CircuitBreakEMA types.IntervalWindow `json:"circuitBreakEMA"`
|
||
|
|
||
|
positionRiskControl *riskcontrol.PositionRiskControl
|
||
|
circuitBreakRiskControl *riskcontrol.CircuitBreakRiskControl
|
||
|
}
|
||
|
|
||
|
// Strategy provides the core functionality that is required by a long/short strategy.
|
||
|
type Strategy struct {
|
||
|
Position *types.Position `json:"position,omitempty" persistence:"position"`
|
||
|
ProfitStats *types.ProfitStats `json:"profitStats,omitempty" persistence:"profit_stats"`
|
||
|
|
||
|
parent, ctx context.Context
|
||
|
cancel context.CancelFunc
|
||
|
|
||
|
Environ *qbtrade.Environment
|
||
|
Session *qbtrade.ExchangeSession
|
||
|
OrderExecutor *qbtrade.GeneralOrderExecutor
|
||
|
|
||
|
RiskController
|
||
|
}
|
||
|
|
||
|
func (s *Strategy) Initialize(ctx context.Context, environ *qbtrade.Environment, session *qbtrade.ExchangeSession, market types.Market, strategyID, instanceID string) {
|
||
|
s.parent = ctx
|
||
|
s.ctx, s.cancel = context.WithCancel(ctx)
|
||
|
|
||
|
s.Environ = environ
|
||
|
s.Session = session
|
||
|
|
||
|
if s.ProfitStats == nil {
|
||
|
s.ProfitStats = types.NewProfitStats(market)
|
||
|
}
|
||
|
|
||
|
if s.Position == nil {
|
||
|
s.Position = types.NewPositionFromMarket(market)
|
||
|
}
|
||
|
|
||
|
// Always update the position fields
|
||
|
s.Position.Strategy = strategyID
|
||
|
s.Position.StrategyInstanceID = instanceID
|
||
|
|
||
|
// if anyone of the fee rate is defined, this assumes that both are defined.
|
||
|
// so that zero maker fee could be applied
|
||
|
if session.MakerFeeRate.Sign() > 0 || session.TakerFeeRate.Sign() > 0 {
|
||
|
s.Position.SetExchangeFeeRate(session.ExchangeName, types.ExchangeFee{
|
||
|
MakerFeeRate: session.MakerFeeRate,
|
||
|
TakerFeeRate: session.TakerFeeRate,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
s.OrderExecutor = qbtrade.NewGeneralOrderExecutor(session, market.Symbol, strategyID, instanceID, s.Position)
|
||
|
s.OrderExecutor.BindEnvironment(environ)
|
||
|
s.OrderExecutor.BindProfitStats(s.ProfitStats)
|
||
|
s.OrderExecutor.Bind()
|
||
|
|
||
|
if !s.PositionHardLimit.IsZero() && !s.MaxPositionQuantity.IsZero() {
|
||
|
log.Infof("positionHardLimit and maxPositionQuantity are configured, setting up PositionRiskControl...")
|
||
|
s.positionRiskControl = riskcontrol.NewPositionRiskControl(s.OrderExecutor, s.PositionHardLimit, s.MaxPositionQuantity)
|
||
|
s.positionRiskControl.Initialize(ctx, session)
|
||
|
}
|
||
|
|
||
|
if !s.CircuitBreakLossThreshold.IsZero() {
|
||
|
log.Infof("circuitBreakLossThreshold is configured, setting up CircuitBreakRiskControl...")
|
||
|
s.circuitBreakRiskControl = riskcontrol.NewCircuitBreakRiskControl(
|
||
|
s.Position,
|
||
|
session.Indicators(market.Symbol).EWMA(s.CircuitBreakEMA),
|
||
|
s.CircuitBreakLossThreshold,
|
||
|
s.ProfitStats,
|
||
|
24*time.Hour)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *Strategy) IsHalted(t time.Time) bool {
|
||
|
if s.circuitBreakRiskControl == nil {
|
||
|
return false
|
||
|
}
|
||
|
return s.circuitBreakRiskControl.IsHalted(t)
|
||
|
}
|