Browse Source

risk metrics

main
strange 10 months ago
parent
commit
41d72b0d0a
  1. 18
      config/exchange.toml
  2. 50
      config/risk.yaml
  3. 6
      internal/trading/backtest/sig_strategy_backtester.go
  4. 3
      pkg/data/entity/trade_plan.go
  5. 494
      pkg/trade/risk/manager.go

18
config/exchange.toml

@ -18,8 +18,8 @@ receiveBuffer = 4096
marketSubscribeLimit = 16 marketSubscribeLimit = 16
consumeBatch = 1024 consumeBatch = 1024
consumeLater = 2000 # 时间到达later或者数据累计到batch触发consume consumeLater = 2000 # 时间到达later或者数据累计到batch触发consume
# httpProxy = "http://192.168.1.5:7890" httpProxy = "http://192.168.1.5:7890"
httpProxy = "http://10.255.183.209:7890" # httpProxy = "http://10.255.183.209:7890"
# 模拟盘API交易地址如下: # 模拟盘API交易地址如下:
# REST:https://www.okx.com # REST:https://www.okx.com
@ -29,3 +29,17 @@ httpProxy = "http://10.255.183.209:7890"
# binance key RYgJrvqP4iGqdRth14r0ChgWo8eg0wPEcFqDttsKzvUJDyhOKvPiz42tXxjYIMiG # binance key RYgJrvqP4iGqdRth14r0ChgWo8eg0wPEcFqDttsKzvUJDyhOKvPiz42tXxjYIMiG
# secret GLTzNNYzC0AbcINPAYfuKDjkWnAMQUhsyd1ed7ubcdzIRrFZBGUrOAkubqyjekVp # secret GLTzNNYzC0AbcINPAYfuKDjkWnAMQUhsyd1ed7ubcdzIRrFZBGUrOAkubqyjekVp
[exchanges.okx]
api_key = "your-api-key"
secret_key = "your-secret-key"
passphrase = "your-passphrase"
[exchanges.binance]
api_key = "your-api-key"
secret_key = "your-secret-key"
[exchanges.bitget]
api_key = "your-api-key"
secret_key = "your-secret-key"
passphrase = "your-passphrase"

50
config/risk.yaml

@ -0,0 +1,50 @@
# 添加风险管理配置
risk_management:
max_position_size: 0.1 # 最大仓位比例
max_drawdown: 0.1 # 最大回撤
stop_loss: 0.05 # 止损比例
take_profit: 0.1 # 止盈比例
max_daily_trades: 50 # 单日最大交易次数
max_daily_loss: 0.05 # 每日最大亏损比例
max_leverage: 3.0 # 最大杠杆倍数
min_volatility: 0.001 # 最小波动率阈值
max_volatility: 0.05 # 最大波动率阈值
correlation_threshold: 0.7 # 相关性阈值
max_concentration: 0.3 # 最大集中度
min_liquidity: 1000000 # 最小流动性要求(USDT)
max_slippage: 0.002 # 最大滑点容忍度
circuit_breaker:
price_change: 0.1 # 价格剧烈变化阈值
time_window: 300 # 监控时间窗口(秒)
cool_down: 600 # 冷却时间(秒)
# 交易频率控制
min_trade_interval: 60 # 最小交易间隔(秒)
max_trades_per_hour: 10 # 每小时最大交易次数
# 价格偏离度控制
max_price_deviation: 0.05 # 与指数价格最大偏离度
reference_exchanges: ["binance", "huobi", "ftx"] # 参考交易所
# 资金利用率控制
max_margin_usage: 0.8 # 最大保证金使用率
min_free_margin: 1000 # 最小剩余保证金(USDT)
# 订单簿深度控制
min_depth_ratio: 0.5 # 最小深度比率
min_bid_ask_size: 10000 # 最小买卖盘大小(USDT)
# 价格趋势控制
trend_window: 24 # 趋势判断窗口(小时)
max_trend_deviation: 0.1 # 最大趋势偏离度
# 波动率分解
volatility_control:
historical_window: 30 # 历史波动率窗口(天)
implied_weight: 0.6 # 隐含波动率权重
realized_weight: 0.4 # 实现波动率权重
# 相关性风险控制
correlation_control:
min_pairs: 3 # 最小对冲币对数量
max_correlation: 0.7 # 最大相关性系数
lookback_period: 30 # 回溯期(天)
# 流动性压力测试
liquidity_stress:
confidence_level: 0.95 # 置信水平
stress_period: 7 # 压力测试期(天)
max_liquidation: 0.2 # 最大清算比例

6
internal/trading/backtest/sig_strategy_backtester.go

@ -12,6 +12,7 @@ import (
"sig-pub/pkg/utils/collect" "sig-pub/pkg/utils/collect"
"sig-pub/pkg/utils/times" "sig-pub/pkg/utils/times"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
"sync/atomic"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -163,6 +164,7 @@ func (b *SigStrategyBacktester) multiIntervalSeries(ctx context.Context, sr *pb.
otherIntervalSyncCh.Set(interval, make(chan int64)) otherIntervalSyncCh.Set(interval, make(chan int64))
} }
stopCh := make(chan struct{}) stopCh := make(chan struct{})
stopChClosed := atomic.Bool{}
for _, interval := range otherIntervals { for _, interval := range otherIntervals {
kSeries := intervalKlineSeries.Get(interval) kSeries := intervalKlineSeries.Get(interval)
if kSeries == nil { if kSeries == nil {
@ -223,8 +225,10 @@ func (b *SigStrategyBacktester) multiIntervalSeries(ctx context.Context, sr *pb.
} else if err1 != io.EOF { } else if err1 != io.EOF {
zlog.Errorf("fetch interval history error: inst=%s(%s) interval=%s, err=%v", sr.InstId, sr.Exchange, interval, err1) zlog.Errorf("fetch interval history error: inst=%s(%s) interval=%s, err=%v", sr.InstId, sr.Exchange, interval, err1)
err = err1 err = err1
if stopChClosed.CompareAndSwap(false, true) {
close(stopCh) close(stopCh)
} }
}
}(interval, kSeries) }(interval, kSeries)
} }
// 驱动周期数据拉取 // 驱动周期数据拉取
@ -274,7 +278,9 @@ func (b *SigStrategyBacktester) multiIntervalSeries(ctx context.Context, sr *pb.
return recvFn(true, driverInterval, k) return recvFn(true, driverInterval, k)
}) })
if err0 != io.EOF { if err0 != io.EOF {
if stopChClosed.CompareAndSwap(false, true) {
close(stopCh) close(stopCh)
}
if err0 != nil { if err0 != nil {
err = err0 err = err0
zlog.Errorf("fetch driver interval history error: inst=%s(%s) interval=%s, err=%v", sr.InstId, sr.Exchange, driverInterval, err0) zlog.Errorf("fetch driver interval history error: inst=%s(%s) interval=%s, err=%v", sr.InstId, sr.Exchange, driverInterval, err0)

3
pkg/data/entity/trade_plan.go

@ -15,9 +15,6 @@ type TradePlan struct {
RiskStrategyParam string `gorm:"column:risk_strategy_param" json:"riskStrategyParam"` // 风险管理策略参数 RiskStrategyParam string `gorm:"column:risk_strategy_param" json:"riskStrategyParam"` // 风险管理策略参数
UpdateBy string `gorm:"column:update_by" json:"updateBy"` // 更新人 UpdateBy string `gorm:"column:update_by" json:"updateBy"` // 更新人
UpdateTime int64 `gorm:"column:update_time" json:"updateTime"` // 更新时间戳毫秒 UpdateTime int64 `gorm:"column:update_time" json:"updateTime"` // 更新时间戳毫秒
// ExitStrategy string `gorm:"column:exit_strategy" json:"exitStrategy"` // 退出策略
// TradeStrategy string `gorm:"column:trade_strategy" json:"tradeStrategy"` // 下单仓位管理策略
} }
func (TradePlan) TableName() string { func (TradePlan) TableName() string {

494
pkg/trade/risk/manager.go

@ -0,0 +1,494 @@
package risk
import (
"context"
"errors"
"fmt"
"math"
"sync"
"time"
)
type Manager struct {
mu sync.RWMutex
maxPositionSize float64
maxDrawdown float64
stopLoss float64
takeProfit float64
maxDailyTrades int
maxDailyLoss float64
maxLeverage float64
minVolatility float64
maxVolatility float64
correlationThreshold float64
maxConcentration float64
minLiquidity float64
maxSlippage float64
circuitBreaker CircuitBreaker
positions map[string]Position
dailyStats DailyStats
volatilityWindow []float64
priceHistory map[string][]PricePoint
minTradeInterval time.Duration
maxTradesPerHour int
maxPriceDeviation float64
referenceExchanges []string
maxMarginUsage float64
minFreeMargin float64
minDepthRatio float64
minBidAskSize float64
trendWindow time.Duration
maxTrendDeviation float64
volatilityControl VolatilityControl
correlationControl CorrelationControl
liquidityStress LiquidityStress
lastTradeTime time.Time
hourlyTradeCount int
hourlyTradeReset time.Time
}
type CircuitBreaker struct {
PriceChangeThreshold float64
TimeWindow time.Duration
CoolDown time.Duration
LastTriggered time.Time
IsTriggered bool
}
type DailyStats struct {
Date time.Time
TradeCount int
TotalPnL float64
HighestPrice float64
LowestPrice float64
}
type PricePoint struct {
Price float64
Volume float64
Timestamp time.Time
}
type Position struct {
Symbol string
EntryPrice float64
Amount float64
Leverage float64
UnrealizedPnL float64
OpenTime time.Time
}
type VolatilityControl struct {
HistoricalWindow int
ImpliedWeight float64
RealizedWeight float64
HistoricalVol float64
ImpliedVol float64
}
type CorrelationControl struct {
MinPairs int
MaxCorrelation float64
LookbackPeriod int
PairCorrelations map[string]map[string]float64
}
type LiquidityStress struct {
ConfidenceLevel float64
StressPeriod int
MaxLiquidation float64
StressScenarios []StressScenario
}
type StressScenario struct {
PriceChange float64
VolumeChange float64
SpreadChange float64
Probability float64
}
func NewManager(config map[string]float64) *Manager {
return &Manager{
maxPositionSize: config["max_position_size"],
maxDrawdown: config["max_drawdown"],
stopLoss: config["stop_loss"],
takeProfit: config["take_profit"],
positions: make(map[string]Position),
}
}
func (m *Manager) CheckAndUpdatePosition(ctx context.Context, order exchange.Order) error {
m.mu.Lock()
defer m.mu.Unlock()
// Check position size
if order.Amount > m.maxPositionSize*m.currentBalance {
return errors.New("order exceeds maximum position size")
}
// Check drawdown
if m.currentBalance < m.initialBalance*(1-m.maxDrawdown) {
return errors.New("maximum drawdown reached")
}
return nil
}
func (m *Manager) UpdatePositionPrice(symbol string, currentPrice float64) (bool, error) {
m.mu.Lock()
defer m.mu.Unlock()
pos, exists := m.positions[symbol]
if !exists {
return false, nil
}
// Calculate unrealized PnL
pnlPercent := (currentPrice - pos.EntryPrice) / pos.EntryPrice
// Check stop loss
if pnlPercent <= -m.stopLoss {
return true, nil // Should close position
}
// Check take profit
if pnlPercent >= m.takeProfit {
return true, nil // Should close position
}
return false, nil
}
func (m *Manager) CheckRisk(ctx context.Context, order exchange.Order, marketData exchange.MarketData) error {
m.mu.Lock()
defer m.mu.Unlock()
// 1. 检查每日交易次数限制
if m.dailyStats.TradeCount >= m.maxDailyTrades {
return errors.New("daily trade limit exceeded")
}
// 2. 检查每日亏损限制
if m.dailyStats.TotalPnL <= -m.maxDailyLoss*m.initialBalance {
return errors.New("daily loss limit reached")
}
// 3. 检查杠杆率
if order.Leverage > m.maxLeverage {
return errors.New("leverage exceeds maximum allowed")
}
// 4. 检查波动率
volatility := m.calculateVolatility()
if volatility < m.minVolatility {
return errors.New("market volatility too low")
}
if volatility > m.maxVolatility {
return errors.New("market volatility too high")
}
// 5. 检查流动性
if !m.checkLiquidity(marketData) {
return errors.New("insufficient market liquidity")
}
// 6. 检查集中度
if !m.checkConcentration(order) {
return errors.New("position concentration too high")
}
// 7. 检查熔断机制
if m.checkCircuitBreaker(marketData) {
return errors.New("circuit breaker triggered")
}
// 8. 检查滑点
if !m.checkSlippage(order, marketData) {
return errors.New("expected slippage too high")
}
// 交易频率检查
if err := m.checkTradeFrequency(); err != nil {
return err
}
// 价格偏离度检查
if err := m.checkPriceDeviation(marketData); err != nil {
return err
}
// 保证金使用率检查
if err := m.checkMarginUsage(order); err != nil {
return err
}
// 订单簿深度检查
if err := m.checkOrderBookDepth(marketData); err != nil {
return err
}
// 趋势偏离检查
if err := m.checkTrendDeviation(marketData); err != nil {
return err
}
// 综合波动率检查
if err := m.checkCompositeVolatility(marketData); err != nil {
return err
}
// 相关性风险检查
if err := m.checkCorrelationRisk(marketData); err != nil {
return err
}
// 流动性压力测试
if err := m.checkLiquidityStress(order, marketData); err != nil {
return err
}
return nil
}
func (m *Manager) calculateVolatility() float64 {
if len(m.volatilityWindow) < 2 {
return 0
}
// 计算对数收益率
returns := make([]float64, len(m.volatilityWindow)-1)
for i := 1; i < len(m.volatilityWindow); i++ {
returns[i-1] = math.Log(m.volatilityWindow[i] / m.volatilityWindow[i-1])
}
// 计算标准差
mean := 0.0
for _, r := range returns {
mean += r
}
mean /= float64(len(returns))
variance := 0.0
for _, r := range returns {
variance += math.Pow(r-mean, 2)
}
variance /= float64(len(returns))
return math.Sqrt(variance)
}
func (m *Manager) checkLiquidity(data exchange.MarketData) bool {
// 检查24小时成交量是否满足最小流动性要求
return data.Volume*data.Price >= m.minLiquidity
}
func (m *Manager) checkConcentration(order exchange.Order) bool {
totalPositionValue := 0.0
for _, pos := range m.positions {
totalPositionValue += pos.Amount * pos.EntryPrice
}
// 添加新订单的价值
newPositionValue := order.Amount * order.Price
totalPositionValue += newPositionValue
// 检查单个仓位是否超过总仓位的最大集中度
for _, pos := range m.positions {
positionValue := pos.Amount * pos.EntryPrice
if positionValue/totalPositionValue > m.maxConcentration {
return false
}
}
return true
}
func (m *Manager) checkCircuitBreaker(data exchange.MarketData) bool {
if m.circuitBreaker.IsTriggered {
if time.Since(m.circuitBreaker.LastTriggered) > m.circuitBreaker.CoolDown {
m.circuitBreaker.IsTriggered = false
return false
}
return true
}
// 检查价格变化
priceHistory := m.priceHistory[data.Symbol]
if len(priceHistory) == 0 {
return false
}
timeWindow := time.Now().Add(-m.circuitBreaker.TimeWindow)
var oldPrice float64
for i := len(priceHistory) - 1; i >= 0; i-- {
if priceHistory[i].Timestamp.Before(timeWindow) {
oldPrice = priceHistory[i].Price
break
}
}
if oldPrice > 0 {
priceChange := math.Abs(data.Price-oldPrice) / oldPrice
if priceChange > m.circuitBreaker.PriceChangeThreshold {
m.circuitBreaker.IsTriggered = true
m.circuitBreaker.LastTriggered = time.Now()
return true
}
}
return false
}
func (m *Manager) checkSlippage(order exchange.Order, data exchange.MarketData) bool {
expectedSlippage := math.Abs(order.Price-data.Price) / data.Price
return expectedSlippage <= m.maxSlippage
}
func (m *Manager) UpdateDailyStats(pnl float64) {
today := time.Now().UTC().Truncate(24 * time.Hour)
if m.dailyStats.Date != today {
// Reset daily stats
m.dailyStats = DailyStats{
Date: today,
TradeCount: 0,
TotalPnL: 0,
HighestPrice: 0,
LowestPrice: math.MaxFloat64,
}
}
m.dailyStats.TradeCount++
m.dailyStats.TotalPnL += pnl
}
func (m *Manager) UpdatePriceHistory(data exchange.MarketData) {
if m.priceHistory == nil {
m.priceHistory = make(map[string][]PricePoint)
}
pricePoint := PricePoint{
Price: data.Price,
Volume: data.Volume,
Timestamp: data.Timestamp,
}
// 保持价格历史在合理范围内
history := m.priceHistory[data.Symbol]
if len(history) > 1000 {
history = history[1:]
}
history = append(history, pricePoint)
m.priceHistory[data.Symbol] = history
// 更新波动率窗口
if len(m.volatilityWindow) > 100 {
m.volatilityWindow = m.volatilityWindow[1:]
}
m.volatilityWindow = append(m.volatilityWindow, data.Price)
}
func (m *Manager) checkTradeFrequency() error {
now := time.Now()
// 检查最小交易间隔
if now.Sub(m.lastTradeTime) < m.minTradeInterval {
return errors.New("trade frequency too high")
}
// 检查每小时交易次数
if now.Sub(m.hourlyTradeReset) >= time.Hour {
m.hourlyTradeCount = 0
m.hourlyTradeReset = now
}
if m.hourlyTradeCount >= m.maxTradesPerHour {
return errors.New("hourly trade limit exceeded")
}
return nil
}
func (m *Manager) checkPriceDeviation(data exchange.MarketData) error {
// 获取参考价格
var prices []float64
for _, ex := range m.referenceExchanges {
price, err := m.getPriceFromExchange(ex, data.Symbol)
if err != nil {
continue
}
prices = append(prices, price)
}
if len(prices) == 0 {
return nil // 无法获取参考价格时暂时跳过检查
}
// 计算平均参考价格
avgPrice := 0.0
for _, p := range prices {
avgPrice += p
}
avgPrice /= float64(len(prices))
// 检查价格偏离度
deviation := math.Abs(data.Price-avgPrice) / avgPrice
if deviation > m.maxPriceDeviation {
return fmt.Errorf("price deviation %.2f%% exceeds limit", deviation*100)
}
return nil
}
func (m *Manager) checkCompositeVolatility(data exchange.MarketData) error {
// 计算历史波动率
historicalVol := m.calculateHistoricalVolatility()
// 获取期权隐含波动率(如果可用)
impliedVol := m.getImpliedVolatility(data.Symbol)
// 计算综合波动率
compositeVol := historicalVol*m.volatilityControl.RealizedWeight +
impliedVol*m.volatilityControl.ImpliedWeight
if compositeVol > m.maxVolatility {
return fmt.Errorf("composite volatility %.2f%% too high", compositeVol*100)
}
return nil
}
func (m *Manager) checkCorrelationRisk(data exchange.MarketData) error {
// 更新相关性矩阵
m.updateCorrelationMatrix(data)
// 检查是否有足够的对冲币对
hedgePairs := m.findHedgePairs(data.Symbol)
if len(hedgePairs) < m.correlationControl.MinPairs {
return errors.New("insufficient hedge pairs")
}
// 检查相关性是否在允许范围内
for _, pair := range hedgePairs {
if corr := m.getCorrelation(data.Symbol, pair); corr > m.correlationControl.MaxCorrelation {
return fmt.Errorf("correlation with %s too high: %.2f", pair, corr)
}
}
return nil
}
func (m *Manager) checkLiquidityStress(order exchange.Order, data exchange.MarketData) error {
// 运行压力测试场景
for _, scenario := range m.liquidityStress.StressScenarios {
// 计算在压力情况下的清算风险
liquidationRisk := m.calculateLiquidationRisk(order, data, scenario)
if liquidationRisk > m.liquidityStress.MaxLiquidation {
return fmt.Errorf("stress test liquidation risk %.2f%% too high", liquidationRisk*100)
}
}
return nil
}
Loading…
Cancel
Save