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;
}
}
}
}