using System; using System.Collections.Generic; using System.Linq; using NT8.Core.Logging; namespace NT8.Core.Analytics { /// /// Strategy performance summary for portfolio optimization. /// public class StrategyPerformance { public string StrategyName { get; set; } public double MeanReturn { get; set; } public double StdDevReturn { get; set; } public double Sharpe { get; set; } public Dictionary Correlations { get; set; } public StrategyPerformance() { Correlations = new Dictionary(); } } /// /// Portfolio allocation optimization result. /// public class AllocationResult { public Dictionary Allocation { get; set; } public double ExpectedSharpe { get; set; } public AllocationResult() { Allocation = new Dictionary(); } } /// /// Optimizes allocations across multiple strategies. /// public class PortfolioOptimizer { private readonly ILogger _logger; public PortfolioOptimizer(ILogger logger) { if (logger == null) throw new ArgumentNullException("logger"); _logger = logger; } /// /// Returns a Sharpe-weighted allocation. /// public AllocationResult OptimizeAllocation(List strategies) { if (strategies == null) throw new ArgumentNullException("strategies"); try { var result = new AllocationResult(); if (strategies.Count == 0) return result; var positive = strategies.Select(s => new { Name = s.StrategyName, Score = Math.Max(0.0001, s.Sharpe) }).ToList(); var total = positive.Sum(s => s.Score); foreach (var s in positive) { result.Allocation[s.Name] = s.Score / total; } result.ExpectedSharpe = CalculatePortfolioSharpe(result.Allocation, strategies); return result; } catch (Exception ex) { _logger.LogError("OptimizeAllocation failed: {0}", ex.Message); throw; } } /// /// Computes approximate portfolio Sharpe. /// public double CalculatePortfolioSharpe(Dictionary allocation, List strategies) { if (allocation == null) throw new ArgumentNullException("allocation"); if (strategies == null) throw new ArgumentNullException("strategies"); try { if (allocation.Count == 0 || strategies.Count == 0) return 0.0; var byName = strategies.ToDictionary(s => s.StrategyName, s => s); var mean = 0.0; foreach (var kv in allocation) { if (byName.ContainsKey(kv.Key)) mean += kv.Value * byName[kv.Key].MeanReturn; } var variance = 0.0; foreach (var i in allocation) { if (!byName.ContainsKey(i.Key)) continue; var si = byName[i.Key]; foreach (var j in allocation) { if (!byName.ContainsKey(j.Key)) continue; var sj = byName[j.Key]; var corr = 0.0; if (i.Key == j.Key) { corr = 1.0; } else if (si.Correlations.ContainsKey(j.Key)) { corr = si.Correlations[j.Key]; } else if (sj.Correlations.ContainsKey(i.Key)) { corr = sj.Correlations[i.Key]; } variance += i.Value * j.Value * si.StdDevReturn * sj.StdDevReturn * corr; } } var std = variance > 0.0 ? Math.Sqrt(variance) : 0.0; if (std <= 0.0) return 0.0; return mean / std; } catch (Exception ex) { _logger.LogError("CalculatePortfolioSharpe failed: {0}", ex.Message); throw; } } /// /// Computes inverse-volatility risk parity allocation. /// public Dictionary RiskParityAllocation(List strategies) { if (strategies == null) throw new ArgumentNullException("strategies"); try { var result = new Dictionary(); if (strategies.Count == 0) return result; var invVol = new Dictionary(); foreach (var s in strategies) { var vol = s.StdDevReturn > 0.000001 ? s.StdDevReturn : 0.000001; invVol[s.StrategyName] = 1.0 / vol; } var total = invVol.Sum(v => v.Value); foreach (var kv in invVol) { result[kv.Key] = kv.Value / total; } return result; } catch (Exception ex) { _logger.LogError("RiskParityAllocation failed: {0}", ex.Message); throw; } } } }