added more functions

This commit is contained in:
Bojan Kucera 2025-06-04 19:27:11 -04:00
parent a53d8d13ca
commit a1c82ae0b8
7 changed files with 648 additions and 177 deletions

View file

@ -389,33 +389,188 @@ export function performanceAttribution(
}
/**
* Calculate efficient frontier points
* Calculate Efficient Frontier points
*/
export function calculateEfficientFrontier(
expectedReturns: number[],
covarianceMatrix: number[][],
numPoints: number = 100
): Array<{ return: number; volatility: number; sharpeRatio: number; weights: number[] }> {
returns: number[][], // Array of return series for each asset
symbols: string[],
riskFreeRate: number = 0.02,
numPoints: number = 50
): Array<{
weights: number[];
expectedReturn: number;
volatility: number;
sharpeRatio: number;
}> {
if (returns.length !== symbols.length || returns.length < 2) return [];
const n = returns.length;
const results: Array<{ weights: number[]; expectedReturn: number; volatility: number; sharpeRatio: number; }> = [];
// Calculate expected returns and covariance matrix
const expectedReturns = returns.map(assetReturns =>
assetReturns.reduce((sum, ret) => sum + ret, 0) / assetReturns.length
);
const covarianceMatrix = calculateCovarianceMatrix(returns);
// Generate target returns from min to max expected return
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);
// Find minimum variance portfolio for target return using quadratic programming (simplified)
const weights = findMinimumVarianceWeights(expectedReturns, covarianceMatrix, targetReturn);
frontierPoints.push({
return: targetReturn,
volatility: result.volatility,
sharpeRatio: result.sharpeRatio,
weights: result.weights
});
if (weights && weights.length === n) {
const portfolioReturn = weights.reduce((sum, w, j) => sum + w * expectedReturns[j], 0);
const portfolioVariance = calculatePortfolioVariance(weights, covarianceMatrix);
const portfolioVolatility = Math.sqrt(portfolioVariance);
const sharpeRatio = portfolioVolatility > 0 ? (portfolioReturn - riskFreeRate) / portfolioVolatility : 0;
results.push({
weights,
expectedReturn: portfolioReturn,
volatility: portfolioVolatility,
sharpeRatio
});
}
}
return frontierPoints;
return results.sort((a, b) => a.volatility - b.volatility);
}
/**
* Find Minimum Variance Portfolio
*/
export function findMinimumVariancePortfolio(
returns: number[][],
symbols: string[]
): PortfolioOptimizationResult | null {
if (returns.length !== symbols.length || returns.length < 2) return null;
const covarianceMatrix = calculateCovarianceMatrix(returns);
const n = returns.length;
// For minimum variance portfolio: w = (Σ^-1 * 1) / (1' * Σ^-1 * 1)
// Simplified implementation using equal weights as starting point
const weights = new Array(n).fill(1 / n);
// Iterative optimization (simplified)
for (let iter = 0; iter < 100; iter++) {
const gradient = calculateVarianceGradient(weights, covarianceMatrix);
const stepSize = 0.01;
// Update weights
for (let i = 0; i < n; i++) {
weights[i] -= stepSize * gradient[i];
}
// Normalize weights to sum to 1
const weightSum = weights.reduce((sum, w) => sum + w, 0);
for (let i = 0; i < n; i++) {
weights[i] = Math.max(0, weights[i] / weightSum);
}
}
const expectedReturns = returns.map(assetReturns =>
assetReturns.reduce((sum, ret) => sum + ret, 0) / assetReturns.length
);
const portfolioReturn = weights.reduce((sum, w, i) => sum + w * expectedReturns[i], 0);
const portfolioVariance = calculatePortfolioVariance(weights, covarianceMatrix);
const portfolioVolatility = Math.sqrt(portfolioVariance);
const sharpeRatio = portfolioVolatility > 0 ? portfolioReturn / portfolioVolatility : 0;
return {
weights,
expectedReturn: portfolioReturn,
volatility: portfolioVolatility,
sharpeRatio,
symbols
};
}
// Helper functions for portfolio optimization
function calculateCovarianceMatrix(returns: number[][]): number[][] {
const n = returns.length;
const matrix: number[][] = [];
for (let i = 0; i < n; i++) {
matrix[i] = [];
for (let j = 0; j < n; j++) {
matrix[i][j] = calculateCovariance(returns[i], returns[j]);
}
}
return matrix;
}
function calculateCovariance(x: number[], y: number[]): number {
if (x.length !== y.length || x.length < 2) return 0;
const n = x.length;
const meanX = x.reduce((sum, val) => sum + val, 0) / n;
const meanY = y.reduce((sum, val) => sum + val, 0) / n;
return x.reduce((sum, val, i) => sum + (val - meanX) * (y[i] - meanY), 0) / (n - 1);
}
// calculatePortfolioVariance is already exported above
function calculateVarianceGradient(weights: number[], covarianceMatrix: number[][]): number[] {
const n = weights.length;
const gradient: number[] = [];
for (let i = 0; i < n; i++) {
let grad = 0;
for (let j = 0; j < n; j++) {
grad += 2 * weights[j] * covarianceMatrix[i][j];
}
gradient[i] = grad;
}
return gradient;
}
function findMinimumVarianceWeights(
expectedReturns: number[],
covarianceMatrix: number[][],
targetReturn: number
): number[] | null {
const n = expectedReturns.length;
// Simplified implementation - in practice would use quadratic programming solver
// Start with equal weights and adjust
const weights = new Array(n).fill(1 / n);
// Iterative adjustment to meet target return constraint
for (let iter = 0; iter < 50; iter++) {
const currentReturn = weights.reduce((sum, w, i) => sum + w * expectedReturns[i], 0);
const returnDiff = targetReturn - currentReturn;
if (Math.abs(returnDiff) < 0.001) break;
// Adjust weights proportionally to expected returns
const totalExpectedReturn = expectedReturns.reduce((sum, r) => sum + Math.abs(r), 0);
for (let i = 0; i < n; i++) {
const adjustment = (returnDiff * Math.abs(expectedReturns[i])) / totalExpectedReturn;
weights[i] = Math.max(0, weights[i] + adjustment * 0.1);
}
// Normalize weights
const weightSum = weights.reduce((sum, w) => sum + w, 0);
if (weightSum > 0) {
for (let i = 0; i < n; i++) {
weights[i] /= weightSum;
}
}
}
return weights;
}