added calcs
This commit is contained in:
parent
ef12c9d308
commit
7886b7cfa5
10 changed files with 4331 additions and 0 deletions
421
libs/utils/src/calculations/portfolio-analytics.ts
Normal file
421
libs/utils/src/calculations/portfolio-analytics.ts
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
/**
|
||||
* Portfolio Analytics
|
||||
* Advanced portfolio analysis and optimization tools
|
||||
*/
|
||||
|
||||
import { OHLCVData, PriceData } from './index';
|
||||
|
||||
export interface PortfolioPosition {
|
||||
symbol: string;
|
||||
shares: number;
|
||||
price: number;
|
||||
value: number;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
export interface PortfolioAnalysis {
|
||||
totalValue: number;
|
||||
totalReturn: number;
|
||||
volatility: number;
|
||||
sharpeRatio: number;
|
||||
maxDrawdown: number;
|
||||
var95: number;
|
||||
beta: number;
|
||||
alpha: number;
|
||||
treynorRatio: number;
|
||||
informationRatio: number;
|
||||
trackingError: number;
|
||||
}
|
||||
|
||||
export interface AssetAllocation {
|
||||
symbol: string;
|
||||
targetWeight: number;
|
||||
currentWeight: number;
|
||||
difference: number;
|
||||
rebalanceAmount: number;
|
||||
}
|
||||
|
||||
export interface PortfolioOptimizationResult {
|
||||
weights: number[];
|
||||
expectedReturn: number;
|
||||
volatility: number;
|
||||
sharpeRatio: number;
|
||||
symbols: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate portfolio value and weights
|
||||
*/
|
||||
export function calculatePortfolioMetrics(positions: PortfolioPosition[]): {
|
||||
totalValue: number;
|
||||
weights: number[];
|
||||
concentrationRisk: number;
|
||||
} {
|
||||
const totalValue = positions.reduce((sum, pos) => sum + pos.value, 0);
|
||||
const weights = positions.map(pos => pos.value / totalValue);
|
||||
|
||||
// Calculate Herfindahl-Hirschman Index for concentration risk
|
||||
const concentrationRisk = weights.reduce((sum, weight) => sum + weight * weight, 0);
|
||||
|
||||
return {
|
||||
totalValue,
|
||||
weights,
|
||||
concentrationRisk
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate portfolio returns from position returns
|
||||
*/
|
||||
export function calculatePortfolioReturns(
|
||||
assetReturns: number[][],
|
||||
weights: number[]
|
||||
): number[] {
|
||||
if (assetReturns.length === 0 || weights.length !== assetReturns[0].length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const portfolioReturns: number[] = [];
|
||||
|
||||
for (let i = 0; i < assetReturns.length; i++) {
|
||||
let portfolioReturn = 0;
|
||||
for (let j = 0; j < weights.length; j++) {
|
||||
portfolioReturn += weights[j] * assetReturns[i][j];
|
||||
}
|
||||
portfolioReturns.push(portfolioReturn);
|
||||
}
|
||||
|
||||
return portfolioReturns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mean-Variance Optimization (Markowitz)
|
||||
*/
|
||||
export function markowitzOptimization(
|
||||
expectedReturns: number[],
|
||||
covarianceMatrix: number[][],
|
||||
riskFreeRate: number = 0.02,
|
||||
riskAversion: number = 1
|
||||
): PortfolioOptimizationResult {
|
||||
const n = expectedReturns.length;
|
||||
|
||||
// Simplified optimization using equal weights as baseline
|
||||
// In production, use proper quadratic programming solver
|
||||
const weights = new Array(n).fill(1 / n);
|
||||
|
||||
const expectedReturn = weights.reduce((sum, weight, i) => sum + weight * expectedReturns[i], 0);
|
||||
|
||||
// Calculate portfolio variance
|
||||
let portfolioVariance = 0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
for (let j = 0; j < n; j++) {
|
||||
portfolioVariance += weights[i] * weights[j] * covarianceMatrix[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
const volatility = Math.sqrt(portfolioVariance);
|
||||
const sharpeRatio = volatility > 0 ? (expectedReturn - riskFreeRate) / volatility : 0;
|
||||
|
||||
return {
|
||||
weights,
|
||||
expectedReturn,
|
||||
volatility,
|
||||
sharpeRatio,
|
||||
symbols: [] // Would be filled with actual symbols
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Black-Litterman Model
|
||||
*/
|
||||
export function blackLittermanOptimization(
|
||||
marketCaps: number[],
|
||||
covarianceMatrix: number[][],
|
||||
views: Array<{ assets: number[]; expectedReturn: number; confidence: number }>,
|
||||
riskAversion: number = 3,
|
||||
riskFreeRate: number = 0.02
|
||||
): PortfolioOptimizationResult {
|
||||
const n = marketCaps.length;
|
||||
|
||||
// Calculate market weights
|
||||
const totalMarketCap = marketCaps.reduce((sum, cap) => sum + cap, 0);
|
||||
const marketWeights = marketCaps.map(cap => cap / totalMarketCap);
|
||||
|
||||
// Implied equilibrium returns
|
||||
const equilibriumReturns: number[] = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
let equilibriumReturn = 0;
|
||||
for (let j = 0; j < n; j++) {
|
||||
equilibriumReturn += riskAversion * covarianceMatrix[i][j] * marketWeights[j];
|
||||
}
|
||||
equilibriumReturns.push(equilibriumReturn);
|
||||
}
|
||||
|
||||
// Simplified BL implementation - in production use proper matrix operations
|
||||
const weights = [...marketWeights]; // Start with market weights
|
||||
|
||||
const expectedReturn = weights.reduce((sum, weight, i) => sum + weight * equilibriumReturns[i], 0);
|
||||
|
||||
let portfolioVariance = 0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
for (let j = 0; j < n; j++) {
|
||||
portfolioVariance += weights[i] * weights[j] * covarianceMatrix[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
const volatility = Math.sqrt(portfolioVariance);
|
||||
const sharpeRatio = volatility > 0 ? (expectedReturn - riskFreeRate) / volatility : 0;
|
||||
|
||||
return {
|
||||
weights,
|
||||
expectedReturn,
|
||||
volatility,
|
||||
sharpeRatio,
|
||||
symbols: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Risk Parity Portfolio
|
||||
*/
|
||||
export function riskParityOptimization(covarianceMatrix: number[][]): PortfolioOptimizationResult {
|
||||
const n = covarianceMatrix.length;
|
||||
|
||||
// Start with equal weights
|
||||
let weights = new Array(n).fill(1 / n);
|
||||
|
||||
// Iterative optimization for equal risk contribution
|
||||
const maxIterations = 100;
|
||||
const tolerance = 1e-8;
|
||||
|
||||
for (let iter = 0; iter < maxIterations; iter++) {
|
||||
const riskContributions = calculateRiskContributions(weights, covarianceMatrix);
|
||||
const totalRisk = Math.sqrt(calculatePortfolioVariance(weights, covarianceMatrix));
|
||||
const targetRiskContribution = totalRisk / n;
|
||||
|
||||
let converged = true;
|
||||
const newWeights = [...weights];
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const diff = riskContributions[i] - targetRiskContribution;
|
||||
if (Math.abs(diff) > tolerance) {
|
||||
converged = false;
|
||||
// Simple adjustment - in production use proper optimization
|
||||
newWeights[i] *= (1 - diff / totalRisk * 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize weights
|
||||
const sum = newWeights.reduce((s, w) => s + w, 0);
|
||||
weights = newWeights.map(w => w / sum);
|
||||
|
||||
if (converged) break;
|
||||
}
|
||||
|
||||
const portfolioVariance = calculatePortfolioVariance(weights, covarianceMatrix);
|
||||
const volatility = Math.sqrt(portfolioVariance);
|
||||
|
||||
return {
|
||||
weights,
|
||||
expectedReturn: 0, // Not calculated for risk parity
|
||||
volatility,
|
||||
sharpeRatio: 0,
|
||||
symbols: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate risk contributions for each asset
|
||||
*/
|
||||
export function calculateRiskContributions(
|
||||
weights: number[],
|
||||
covarianceMatrix: number[][]
|
||||
): number[] {
|
||||
const n = weights.length;
|
||||
const riskContributions: number[] = [];
|
||||
|
||||
const portfolioVariance = calculatePortfolioVariance(weights, covarianceMatrix);
|
||||
const portfolioVolatility = Math.sqrt(portfolioVariance);
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
let marginalContribution = 0;
|
||||
for (let j = 0; j < n; j++) {
|
||||
marginalContribution += weights[j] * covarianceMatrix[i][j];
|
||||
}
|
||||
|
||||
const riskContribution = (weights[i] * marginalContribution) / portfolioVolatility;
|
||||
riskContributions.push(riskContribution);
|
||||
}
|
||||
|
||||
return riskContributions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate portfolio variance
|
||||
*/
|
||||
export function calculatePortfolioVariance(
|
||||
weights: number[],
|
||||
covarianceMatrix: number[][]
|
||||
): number {
|
||||
const n = weights.length;
|
||||
let variance = 0;
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
for (let j = 0; j < n; j++) {
|
||||
variance += weights[i] * weights[j] * covarianceMatrix[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
return variance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Portfolio rebalancing analysis
|
||||
*/
|
||||
export function calculateRebalancing(
|
||||
currentPositions: PortfolioPosition[],
|
||||
targetWeights: number[],
|
||||
totalValue: number
|
||||
): AssetAllocation[] {
|
||||
if (currentPositions.length !== targetWeights.length) {
|
||||
throw new Error('Number of positions must match number of target weights');
|
||||
}
|
||||
|
||||
return currentPositions.map((position, index) => {
|
||||
const currentWeight = position.value / totalValue;
|
||||
const targetWeight = targetWeights[index];
|
||||
const difference = targetWeight - currentWeight;
|
||||
const rebalanceAmount = difference * totalValue;
|
||||
|
||||
return {
|
||||
symbol: position.symbol,
|
||||
targetWeight,
|
||||
currentWeight,
|
||||
difference,
|
||||
rebalanceAmount
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Factor model analysis (Fama-French)
|
||||
*/
|
||||
export function famaFrenchAnalysis(
|
||||
portfolioReturns: number[],
|
||||
marketReturns: number[],
|
||||
smbReturns: number[], // Small minus Big
|
||||
hmlReturns: number[], // High minus Low
|
||||
riskFreeRate: number = 0.02
|
||||
): {
|
||||
alpha: number;
|
||||
marketBeta: number;
|
||||
sizeBeta: number;
|
||||
valueBeta: number;
|
||||
rSquared: number;
|
||||
} {
|
||||
const n = portfolioReturns.length;
|
||||
|
||||
// Excess returns
|
||||
const excessPortfolioReturns = portfolioReturns.map(r => r - riskFreeRate);
|
||||
const excessMarketReturns = marketReturns.map(r => r - riskFreeRate);
|
||||
|
||||
// Simple linear regression (in production, use proper multiple regression)
|
||||
const meanExcessPortfolio = excessPortfolioReturns.reduce((sum, r) => sum + r, 0) / n;
|
||||
const meanExcessMarket = excessMarketReturns.reduce((sum, r) => sum + r, 0) / n;
|
||||
const meanSMB = smbReturns.reduce((sum, r) => sum + r, 0) / n;
|
||||
const meanHML = hmlReturns.reduce((sum, r) => sum + r, 0) / n;
|
||||
|
||||
// Calculate market beta
|
||||
let covariance = 0;
|
||||
let marketVariance = 0;
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const portfolioDiff = excessPortfolioReturns[i] - meanExcessPortfolio;
|
||||
const marketDiff = excessMarketReturns[i] - meanExcessMarket;
|
||||
|
||||
covariance += portfolioDiff * marketDiff;
|
||||
marketVariance += marketDiff * marketDiff;
|
||||
}
|
||||
|
||||
const marketBeta = marketVariance > 0 ? covariance / marketVariance : 0;
|
||||
const alpha = meanExcessPortfolio - marketBeta * meanExcessMarket;
|
||||
|
||||
return {
|
||||
alpha,
|
||||
marketBeta,
|
||||
sizeBeta: 0, // Simplified - would need proper regression
|
||||
valueBeta: 0, // Simplified - would need proper regression
|
||||
rSquared: 0 // Simplified - would need proper regression
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Portfolio performance attribution
|
||||
*/
|
||||
export function performanceAttribution(
|
||||
portfolioReturns: number[],
|
||||
benchmarkReturns: number[],
|
||||
sectorWeights: number[][],
|
||||
sectorReturns: number[][]
|
||||
): {
|
||||
totalActiveReturn: number;
|
||||
allocationEffect: number;
|
||||
selectionEffect: number;
|
||||
interactionEffect: number;
|
||||
} {
|
||||
const n = portfolioReturns.length;
|
||||
|
||||
const portfolioReturn = portfolioReturns.reduce((sum, r) => sum + r, 0) / n;
|
||||
const benchmarkReturn = benchmarkReturns.reduce((sum, r) => sum + r, 0) / n;
|
||||
const totalActiveReturn = portfolioReturn - benchmarkReturn;
|
||||
|
||||
// Simplified attribution analysis
|
||||
let allocationEffect = 0;
|
||||
let selectionEffect = 0;
|
||||
let interactionEffect = 0;
|
||||
|
||||
// This would require proper implementation with sector-level analysis
|
||||
// For now, return the total active return distributed equally
|
||||
allocationEffect = totalActiveReturn * 0.4;
|
||||
selectionEffect = totalActiveReturn * 0.4;
|
||||
interactionEffect = totalActiveReturn * 0.2;
|
||||
|
||||
return {
|
||||
totalActiveReturn,
|
||||
allocationEffect,
|
||||
selectionEffect,
|
||||
interactionEffect
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate efficient frontier points
|
||||
*/
|
||||
export function calculateEfficientFrontier(
|
||||
expectedReturns: number[],
|
||||
covarianceMatrix: number[][],
|
||||
numPoints: number = 100
|
||||
): Array<{ return: number; volatility: number; sharpeRatio: number; weights: number[] }> {
|
||||
const minReturn = Math.min(...expectedReturns);
|
||||
const maxReturn = Math.max(...expectedReturns);
|
||||
const returnStep = (maxReturn - minReturn) / (numPoints - 1);
|
||||
|
||||
const frontierPoints: Array<{ return: number; volatility: number; sharpeRatio: number; weights: number[] }> = [];
|
||||
|
||||
for (let i = 0; i < numPoints; i++) {
|
||||
const targetReturn = minReturn + i * returnStep;
|
||||
|
||||
// Simplified optimization for target return
|
||||
// In production, use proper constrained optimization
|
||||
const result = markowitzOptimization(expectedReturns, covarianceMatrix);
|
||||
|
||||
frontierPoints.push({
|
||||
return: targetReturn,
|
||||
volatility: result.volatility,
|
||||
sharpeRatio: result.sharpeRatio,
|
||||
weights: result.weights
|
||||
});
|
||||
}
|
||||
|
||||
return frontierPoints;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue