added calcs

This commit is contained in:
Bojan Kucera 2025-06-03 14:54:02 -04:00
parent ef12c9d308
commit 7886b7cfa5
10 changed files with 4331 additions and 0 deletions

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