using System.Text.Json; using AssetManager.Data; using AssetManager.Infrastructure.Services; using AssetManager.Models.DTOs; using Microsoft.Extensions.Logging; namespace AssetManager.Infrastructure.StrategyEngine.Calculators; /// /// 风险平价策略计算器 /// public class RiskParityCalculator : IStrategyCalculator { private readonly IMarketDataService _marketDataService; private readonly ILogger _logger; public string StrategyType => AssetManager.Models.DTOs.StrategyType.RiskParity; public RiskParityCalculator(IMarketDataService marketDataService, ILogger logger) { _marketDataService = marketDataService; _logger = logger; } public async Task CalculateAsync( string configJson, List positions, CancellationToken cancellationToken = default) { var config = JsonSerializer.Deserialize(configJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new RiskParityConfig(); // 确定目标资产列表 var targetAssets = new List(); var userDefinedWeights = new Dictionary(); if (config.Assets?.Count > 0) { targetAssets = config.Assets.Select(a => a.Symbol).ToList(); userDefinedWeights = config.Assets.ToDictionary(a => a.Symbol, a => a.TargetWeight); } else { targetAssets = positions.Select(p => p.StockCode).ToList(); } if (targetAssets.Count == 0) { return new StrategySignal { StrategyType = StrategyType, Signal = "HOLD", Reason = "无目标资产", GeneratedAt = DateTime.UtcNow }; } // 计算资产的波动率和目标权重 var assetVolatilities = new Dictionary(); var positionSignals = new List(); foreach (var symbol in targetAssets) { cancellationToken.ThrowIfCancellationRequested(); try { var volatility = await CalculateVolatilityAsync(symbol, config.LookbackPeriod, cancellationToken); assetVolatilities[symbol] = volatility; } catch (Exception ex) { _logger.LogError(ex, "计算 {Symbol} 的波动率失败", symbol); assetVolatilities[symbol] = 0.2m; // 默认波动率20% } } // 计算风险平价的目标权重,若无则等权初始化 var targetWeights = CalculateRiskParityWeights(assetVolatilities); // 如果用户定义了权重,则使用用户定义的权重 foreach (var kvp in userDefinedWeights) { targetWeights[kvp.Key] = kvp.Value; } // 计算当前权重 var currentWeights = CalculateCurrentWeights(positions, targetAssets); // 根据偏差决定是否再平衡 var maxDeviation = 0m; var needRebalance = false; var reasons = new List(); foreach (var symbol in targetAssets) { var targetWeight = targetWeights.GetValueOrDefault(symbol, 0m); var currentWeight = currentWeights.GetValueOrDefault(symbol, 0m); var deviation = Math.Abs(targetWeight - currentWeight); if (deviation > config.RebalanceThreshold) { needRebalance = true; } maxDeviation = Math.Max(maxDeviation, deviation); var positionSignal = new PositionSignal { Symbol = symbol, TargetWeight = targetWeight, Signal = deviation > config.RebalanceThreshold ? (targetWeight > currentWeight ? "BUY" : "SELL") : "HOLD", Reason = $"目标权重: {targetWeight:P2}, 当前权重: {currentWeight:P2}, 偏差: {deviation:P2}" }; positionSignals.Add(positionSignal); if (deviation > config.RebalanceThreshold) { reasons.Add($"{symbol}: 调整 {(targetWeight > currentWeight ? "+" : "")}{(targetWeight - currentWeight):P2}"); } } return new StrategySignal { StrategyType = StrategyType, Signal = needRebalance ? "REBALANCE" : "HOLD", PositionSignals = positionSignals, Reason = needRebalance ? $"最大偏差 {maxDeviation:P2} 超过阈值 {config.RebalanceThreshold:P2}。{string.Join("; ", reasons)}" : $"资产偏差在阈值范围内。(最大偏差: {maxDeviation:P2})", GeneratedAt = DateTime.UtcNow }; } /// /// 计算资产波动率(年化标准差) /// private async Task CalculateVolatilityAsync( string symbol, int lookbackPeriod, CancellationToken cancellationToken) { var historicalData = await _marketDataService.GetStockHistoricalDataAsync( symbol, "1d", lookbackPeriod + 1); if (historicalData.Count < 2) { throw new InvalidOperationException($"历史数据不足以计算波动率"); } // 按时间排序并计算日收益率 var sortedData = historicalData.OrderBy(d => d.Timestamp).ToList(); var returns = new List(); for (int i = 1; i < sortedData.Count; i++) { var dailyReturn = (sortedData[i].Close - sortedData[i - 1].Close) / sortedData[i - 1].Close; returns.Add(dailyReturn); } // 计算标准差 var avgReturn = returns.Average(); var variance = returns.Sum(r => (r - avgReturn) * (r - avgReturn)) / returns.Count; var dailyStdDev = (decimal)Math.Sqrt((double)variance); // 年化波动率,假设252个交易日 return dailyStdDev * (decimal)Math.Sqrt(252); } /// /// 计算风险平价权重,若无则等权初始化 /// private Dictionary CalculateRiskParityWeights(Dictionary volatilities) { var weights = new Dictionary(); // 计算倒数波动率之和 var inverseVolSum = volatilities.Values.Where(v => v > 0).Sum(v => 1m / v); if (inverseVolSum == 0) { // 如果无法计算,采用等权分配 var equalWeight = 1m / volatilities.Count; foreach (var symbol in volatilities.Keys) { weights[symbol] = equalWeight; } } else { foreach (var kvp in volatilities) { weights[kvp.Key] = kvp.Value > 0 ? (1m / kvp.Value) / inverseVolSum : 0m; } } return weights; } /// /// 计算当前持仓权重 /// private Dictionary CalculateCurrentWeights(List positions, List targetAssets) { var weights = new Dictionary(); // 计算总价值 var totalValue = positions.Sum(p => p.Shares * p.AvgPrice); foreach (var symbol in targetAssets) { var position = positions.FirstOrDefault(p => p.StockCode == symbol); if (position != null && totalValue > 0) { weights[symbol] = (position.Shares * position.AvgPrice) / totalValue; } else { weights[symbol] = 0m; } } return weights; } }