Готовая система инвестиционного анализа на C#
Для автоматизации инвестиционного анализа на фриланс-платформе мы разработали модульную систему на C#, которая включает все необходимые компоненты для оценки активов, расчета рисков и оптимизации портфелей.
namespace FreelancePlatform.InvestmentAnalysis
{
public enum AssetClass
{
GrowthStocks,
DividendStocks,
Bonds,
Cryptocurrency,
REITs,
VentureCapital,
Commodities,
ETFs
}
public enum RiskLevel
{
Conservative = 1,
Moderate = 2,
Balanced = 3,
Growth = 4,
Aggressive = 5
}
public class FinancialAsset
{
public string Ticker { get; set; }
public string Name { get; set; }
public AssetClass AssetClass { get; set; }
public decimal CurrentPrice { get; set; }
public decimal MarketCap { get; set; }
public List<decimal> HistoricalPrices { get; set; } = new();
public decimal DividendYield { get; set; }
public decimal PERatio { get; set; }
public decimal Beta { get; set; }
public decimal CalculateHistoricalReturn(int periodDays = 365)
{
if (HistoricalPrices.Count() < 2) return 0;
int startIndex = Math.Max(0, HistoricalPrices.Count() - periodDays - 1);
decimal startPrice = HistoricalPrices[startIndex];
decimal endPrice = HistoricalPrices[^1];
return (endPrice - startPrice) / startPrice;
}
public decimal CalculateVolatility(int periodDays = 90)
{
if (HistoricalPrices.Count() < periodDays) return 0;
var returns = new List<decimal>();
for (int i = 1; i < Math.Min(periodDays, HistoricalPrices.Count()); i++)
{
decimal dailyReturn = (HistoricalPrices[i] - HistoricalPrices[i - 1]) / HistoricalPrices[i - 1];
returns.Add(dailyReturn);
}
decimal mean = returns.Average();
decimal sumOfSquares = returns.Sum(r => Pow(r - mean, 2));
decimal variance = sumOfSquares / (returns.Count() - 1);
return Sqrt((double)variance);
}
private decimal Pow(decimal value, int power)
{
return (decimal)Math.Pow((double)value, power);
}
private decimal Sqrt(double value)
{
return (decimal)Math.Sqrt(value);
}
public decimal CalculateDCFValue(
decimal freeCashFlow,
decimal growthRate,
decimal discountRate,
int projectionYears = 5)
{
decimal presentValue = 0;
decimal futureCashFlow = freeCashFlow;
for (int year = 1; year <= projectionYears; year++)
{
futureCashFlow *= (1 + growthRate);
decimal discountFactor = 1 / Pow(1 + discountRate, year);
presentValue += futureCashFlow * discountFactor;
}
decimal terminalValue = futureCashFlow * (1 + growthRate) / (discountRate - growthRate);
decimal terminalDiscount = 1 / Pow(1 + discountRate, projectionYears);
return presentValue + (terminalValue * terminalDiscount);
}
}
}
Класс для построения и оптимизации портфеля
public class InvestmentPortfolio
{
public string PortfolioName { get; set; }
public string ClientName { get; set; }
public RiskLevel TargetRiskLevel { get; set; }
public decimal TotalValue { get; set; }
public List<PortfolioItem> Holdings { get; set; } = new();
public DateTime CreatedDate { get; set; }
public decimal CalculatePortfolioReturn(int periodDays = 365)
{
if (!Holdings.Any()) return 0;
decimal weightedReturn = 0;
foreach (var holding in Holdings)
{
decimal assetReturn = holding.Asset.CalculateHistoricalReturn(periodDays);
decimal weight = holding.Value / TotalValue;
weightedReturn += assetReturn * weight;
}
return weightedReturn;
}
public decimal CalculatePortfolioRisk()
{
if (!Holdings.Any()) return 0;
decimal portfolioVariance = 0;
int n = Holdings.Count();
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
decimal weightI = Holdings[i].Value / TotalValue;
decimal weightJ = Holdings[j].Value / TotalValue;
decimal volatilityI = Holdings[i].Asset.CalculateVolatility();
decimal volatilityJ = Holdings[j].Asset.CalculateVolatility();
decimal correlation = EstimateCorrelation(Holdings[i].Asset, Holdings[j].Asset);
portfolioVariance += weightI * weightJ * volatilityI * volatilityJ * correlation;
}
}
return (decimal)Math.Sqrt((double)portfolioVariance);
}
public decimal CalculateSharpeRatio(decimal riskFreeRate = 0.05m)
{
decimal portfolioReturn = CalculatePortfolioReturn();
decimal portfolioRisk = CalculatePortfolioRisk();
if (portfolioRisk == 0) return 0;
return (portfolioReturn - riskFreeRate) / portfolioRisk;
}
public InvestmentPortfolio OptimizePortfolio()
{
var optimizedHoldings = new List<PortfolioItem>();
foreach (var holding in Holdings)
{
decimal optimalWeight = TargetRiskLevel switch
{
RiskLevel.Conservative => 0.1m,
RiskLevel.Moderate => 0.15m,
RiskLevel.Balanced => 0.2m,
RiskLevel.Growth => 0.25m,
RiskLevel.Aggressive => 0.3m,
_ => 0.15m
};
decimal sharpeRatio = CalculateSharpeRatio();
if (sharpeRatio < 1)
optimalWeight *= 0.8m;
else if (sharpeRatio > 2)
optimalWeight *= 1.2m;
var optimizedHolding = new PortfolioItem
{
Asset = holding.Asset,
Value = TotalValue * optimalWeight,
PurchaseDate = holding.PurchaseDate,
PurchasePrice = holding.PurchasePrice
};
optimizedHoldings.Add(optimizedHolding);
}
decimal totalOptimizedValue = optimizedHoldings.Sum(h => h.Value);
foreach (var holding in optimizedHoldings)
{
holding.Value = (holding.Value / totalOptimizedValue) * TotalValue;
}
return new InvestmentPortfolio
{
PortfolioName = $"Оптимизированный {PortfolioName}",
ClientName = ClientName,
TargetRiskLevel = TargetRiskLevel,
TotalValue = TotalValue,
Holdings = optimizedHoldings,
CreatedDate = DateTime.Now
};
}
private decimal EstimateCorrelation(FinancialAsset asset1, FinancialAsset asset2)
{
if (asset1.AssetClass == asset2.AssetClass) return 0.7m;
var correlatedClasses = new List<(AssetClass, AssetClass, decimal)>
{
(AssetClass.GrowthStocks, AssetClass.ETFs, 0.6m),
(AssetClass.Bonds, AssetClass.REITs, -0.3m),
(AssetClass.Cryptocurrency, AssetClass.Commodities, 0.4m)
};
var correlation = correlatedClasses
.FirstOrDefault(c =>
(c.Item1 == asset1.AssetClass && c.Item2 == asset2.AssetClass) ||
(c.Item1 == asset2.AssetClass && c.Item2 == asset1.AssetClass));
return correlation != default ? correlation.Item3 : 0.1m;
}
}
public class PortfolioItem
{
public FinancialAsset Asset { get; set; }
public decimal Value { get; setpublic DateTime PurchaseDate { get; setpublic decimal PurchasePrice { get; setpublic decimal CurrentValue => Asset.CurrentPrice * (Value / PurchasePrice);
public decimal ProfitLoss => CurrentValue - Value;
public decimal ProfitLossPercentage => Value != 0 ? ProfitLoss / Value * 100 : 0;
}
Система оценки рисков и стресс-тестирования
public class RiskAnalyzer
{
public RiskAnalysis AnalyzePortfolioRisks(InvestmentPortfolio portfolio)
{
var analysis = new RiskAnalysis
{
PortfolioName = portfolio.PortfolioName,
AnalysisDate = DateTime.Now,
TotalValue = portfolio.TotalValue
};
analysis.ValueAtRisk = CalculateVaR(portfolio, confidenceLevel: 0.95m);
analysis.ConditionalVaR = CalculateCVaR(portfolio, confidenceLevel: 0.95m);
analysis.MaxDrawdown = CalculateMaxDrawdown(portfolio);
analysis.PortfolioBeta = CalculatePortfolioBeta(portfolio);
analysis.StressTestResults = PerformStressTests(portfolio);
analysis.ConcentrationRisk = CalculateConcentrationRisk(portfolio);
analysis.Recommendations = GenerateRiskRecommendations(analysis);
return analysis;
}
private decimal CalculateVaR(InvestmentPortfolio portfolio, decimal confidenceLevel)
{
var simulatedLosses = new List<decimal>();
foreach (var holding in portfolio.Holdings)
{
decimal volatility = holding.Asset.CalculateVolatility();
decimal zScore = confidenceLevel == 0.95m ? 1.645m : 2.326m;
decimal var = holding.Value * volatility * zScore;
simulatedLosses.Add(var);
}
return simulatedLosses.Sum() * 0.7m;
}
private decimal CalculateCVaR(InvestmentPortfolio portfolio, decimal confidenceLevel)
{
decimal var = CalculateVaR(portfolio, confidenceLevel);
return var * 1.3m;
}
private decimal CalculateMaxDrawdown(InvestmentPortfolio portfolio)
{
decimal maxDrawdown = 0;
decimal peak = portfolio.TotalValue;
for (int i = 0; i < 100; i++)
{
decimal simulatedValue = portfolio.TotalValue * (0.8m + (decimal)new Random().NextDouble() * 0.4m);
if (simulatedValue > peak)
peak = simulatedValue;
decimal drawdown = (peak - simulatedValue) / peak;
if (drawdown > maxDrawdown)
maxDrawdown = drawdown;
}
return maxDrawdown;
}
private decimal CalculatePortfolioBeta(InvestmentPortfolio portfolio)
{
decimal weightedBeta = 0;
foreach (var holding in portfolio.Holdings)
{
decimal weight = holding.Value / portfolio.TotalValue;
weightedBeta += holding.Asset.Beta * weight;
}
return weightedBeta;
}
private List<StressTestResult> PerformStressTests(InvestmentPortfolio portfolio)
{
var results = new List<StressTestResult>();
var scenarios = new List<(string, decimal)>
{
("Рыночный кризис 2008", -0.4m),
("Пандемия COVID-19", -0.35m),
("Рост ставок ЦБ", -0.2m),
("Геополитический кризис", -0.3m),
("Криптовалютный крах", -0.6m)
};
foreach (var scenario in scenarios)
{
decimal scenarioLoss = 0;
foreach (var holding in portfolio.Holdings)
{
decimal sensitivity = holding.Asset.AssetClass switch
{
AssetClass.GrowthStocks => 1.2m,
AssetClass.Cryptocurrency => 1.8m,
AssetClass.Bonds => 0.5m,
AssetClass.REITs => 0.9m,
_ => 1.0m
};
scenarioLoss += holding.Value * scenario.Item2 * sensitivity;
}
results.Add(new StressTestResult
{
ScenarioName = scenario.Item1,
EstimatedLoss = scenarioLoss,
LossPercentage = scenarioLoss / portfolio.TotalValue
});
}
return results;
}
}
public class RiskAnalysis
{
public string PortfolioName { get; set; }
public DateTime AnalysisDate { get; set; }
public decimal TotalValue { get; set; }
public decimal ValueAtRisk { get; set; }
public decimal ConditionalVaR { get; set; }
public decimal MaxDrawdown { get; set; }
public decimal PortfolioBeta { get; set; }
public decimal ConcentrationRisk { get; set; }
public List<StressTestResult> StressTestResults { get; set; } = new();
public List<string> Recommendations { get; set; } = new();
}
Практическое применение системы: Этот код позволяет автоматизировать инвестиционный анализ, оценку рисков и оптимизацию портфелей. Система включает математические модели для расчета доходности, рисков, коэффициента Шарпа, VaR, стресс-тестирования. Может быть интегрирована в финтех-платформы или использована независимыми инвестиционными консультантами.