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.Where(a => a.Symbol != null).Select(a => a.Symbol!).ToList();
userDefinedWeights = config.Assets.Where(a => a.Symbol != null).ToDictionary(a => a.Symbol!, a => a.TargetWeight);
}
else
{
targetAssets = positions.Where(p => p.StockCode != null).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 = await CalculateCurrentWeightsAsync(positions, targetAssets, cancellationToken);
// 根据偏差决定是否再平衡
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 async Task> CalculateCurrentWeightsAsync(
List positions,
List targetAssets,
CancellationToken cancellationToken)
{
var weights = new Dictionary();
var marketValues = new Dictionary();
decimal totalValue = 0;
// 获取每个持仓的实时价格并计算市值
foreach (var position in positions)
{
cancellationToken.ThrowIfCancellationRequested();
if (position.StockCode == null)
{
continue;
}
MarketPriceResponse priceResponse;
if (position.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true)
{
priceResponse = await _marketDataService.GetCryptoPriceAsync(position.StockCode);
}
else
{
priceResponse = await _marketDataService.GetStockPriceAsync(position.StockCode);
}
decimal marketValue = position.Shares * priceResponse.Price;
marketValues[position.StockCode] = marketValue;
totalValue += marketValue;
}
// 计算权重
foreach (var symbol in targetAssets)
{
if (marketValues.TryGetValue(symbol, out var marketValue) && totalValue > 0)
{
weights[symbol] = marketValue / totalValue;
}
else
{
weights[symbol] = 0m;
}
}
return weights;
}
}