Files
wifi-densepose/vendor/ruvector/examples/neural-trader/portfolio/optimization.js

429 lines
14 KiB
JavaScript

/**
* Portfolio Optimization with Neural Trader
*
* Demonstrates using @neural-trader/portfolio for:
* - Mean-Variance Optimization (Markowitz)
* - Risk Parity Portfolio
* - Maximum Sharpe Ratio
* - Minimum Volatility
* - Black-Litterman Model
*/
// Portfolio configuration
const portfolioConfig = {
// Assets to optimize
assets: ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'NVDA', 'META', 'TSLA', 'BRK.B', 'JPM', 'V'],
// Risk-free rate (annual)
riskFreeRate: 0.045,
// Optimization constraints
constraints: {
minWeight: 0.02, // Minimum 2% per asset
maxWeight: 0.25, // Maximum 25% per asset
maxSectorWeight: 0.40, // Maximum 40% per sector
turnoverLimit: 0.20 // Maximum 20% turnover per rebalance
},
// Lookback period for historical data
lookbackDays: 252 * 3 // 3 years
};
// Sector mappings
const sectorMap = {
'AAPL': 'Technology', 'GOOGL': 'Technology', 'MSFT': 'Technology',
'AMZN': 'Consumer', 'NVDA': 'Technology', 'META': 'Technology',
'TSLA': 'Consumer', 'BRK.B': 'Financial', 'JPM': 'Financial', 'V': 'Financial'
};
async function main() {
console.log('='.repeat(70));
console.log('Portfolio Optimization - Neural Trader');
console.log('='.repeat(70));
console.log();
// 1. Load historical returns
console.log('1. Loading historical data...');
const { returns, prices, covariance, expectedReturns } = generateHistoricalData(
portfolioConfig.assets,
portfolioConfig.lookbackDays
);
console.log(` Assets: ${portfolioConfig.assets.length}`);
console.log(` Data points: ${portfolioConfig.lookbackDays} days`);
console.log();
// 2. Display asset statistics
console.log('2. Asset Statistics:');
console.log('-'.repeat(70));
console.log(' Asset | Ann. Return | Volatility | Sharpe | Sector');
console.log('-'.repeat(70));
portfolioConfig.assets.forEach(asset => {
const annReturn = expectedReturns[asset];
const vol = Math.sqrt(covariance[asset][asset]) * Math.sqrt(252);
const sharpe = (annReturn - portfolioConfig.riskFreeRate) / vol;
console.log(` ${asset.padEnd(7)} | ${(annReturn * 100).toFixed(1).padStart(10)}% | ${(vol * 100).toFixed(1).padStart(9)}% | ${sharpe.toFixed(2).padStart(6)} | ${sectorMap[asset]}`);
});
console.log();
// 3. Calculate different portfolio optimizations
console.log('3. Portfolio Optimization Results:');
console.log('='.repeat(70));
// Equal Weight (benchmark)
const equalWeight = equalWeightPortfolio(portfolioConfig.assets);
displayPortfolio('Equal Weight (Benchmark)', equalWeight, expectedReturns, covariance);
// Minimum Variance
const minVar = minimumVariancePortfolio(expectedReturns, covariance, portfolioConfig.constraints);
displayPortfolio('Minimum Variance', minVar, expectedReturns, covariance);
// Maximum Sharpe Ratio
const maxSharpe = maximumSharpePortfolio(expectedReturns, covariance, portfolioConfig.riskFreeRate, portfolioConfig.constraints);
displayPortfolio('Maximum Sharpe Ratio', maxSharpe, expectedReturns, covariance);
// Risk Parity
const riskParity = riskParityPortfolio(covariance);
displayPortfolio('Risk Parity', riskParity, expectedReturns, covariance);
// Black-Litterman
const bl = blackLittermanPortfolio(expectedReturns, covariance, portfolioConfig.constraints);
displayPortfolio('Black-Litterman', bl, expectedReturns, covariance);
// 4. Efficient Frontier
console.log('4. Efficient Frontier:');
console.log('-'.repeat(70));
console.log(' Target Vol | Exp. Return | Sharpe | Weights Summary');
console.log('-'.repeat(70));
const targetVols = [0.10, 0.12, 0.15, 0.18, 0.20, 0.25];
for (const targetVol of targetVols) {
const portfolio = efficientFrontierPoint(expectedReturns, covariance, targetVol, portfolioConfig.constraints);
const ret = calculatePortfolioReturn(portfolio, expectedReturns);
const vol = calculatePortfolioVolatility(portfolio, covariance);
const sharpe = (ret - portfolioConfig.riskFreeRate) / vol;
// Summarize weights
const topWeights = Object.entries(portfolio)
.sort((a, b) => b[1] - a[1])
.slice(0, 3)
.map(([asset, weight]) => `${asset}:${(weight * 100).toFixed(0)}%`)
.join(', ');
console.log(` ${(targetVol * 100).toFixed(0).padStart(9)}% | ${(ret * 100).toFixed(1).padStart(10)}% | ${sharpe.toFixed(2).padStart(6)} | ${topWeights}`);
}
console.log();
// 5. Sector allocation analysis
console.log('5. Sector Allocation Analysis:');
console.log('-'.repeat(70));
const portfolios = {
'Equal Weight': equalWeight,
'Min Variance': minVar,
'Max Sharpe': maxSharpe,
'Risk Parity': riskParity
};
const sectors = [...new Set(Object.values(sectorMap))];
console.log(` Portfolio | ${sectors.map(s => s.padEnd(10)).join(' | ')}`);
console.log('-'.repeat(70));
for (const [name, portfolio] of Object.entries(portfolios)) {
const sectorWeights = {};
sectors.forEach(s => sectorWeights[s] = 0);
for (const [asset, weight] of Object.entries(portfolio)) {
sectorWeights[sectorMap[asset]] += weight;
}
const row = sectors.map(s => (sectorWeights[s] * 100).toFixed(1).padStart(8) + '%').join(' | ');
console.log(` ${name.padEnd(14)} | ${row}`);
}
console.log();
// 6. Rebalancing analysis
console.log('6. Rebalancing Analysis (from Equal Weight):');
console.log('-'.repeat(70));
for (const [name, portfolio] of Object.entries(portfolios)) {
if (name === 'Equal Weight') continue;
let turnover = 0;
for (const asset of portfolioConfig.assets) {
turnover += Math.abs((portfolio[asset] || 0) - equalWeight[asset]);
}
turnover /= 2; // One-way turnover
const numTrades = Object.keys(portfolio).filter(a =>
Math.abs((portfolio[a] || 0) - equalWeight[a]) > 0.01
).length;
console.log(` ${name.padEnd(15)}: ${(turnover * 100).toFixed(1)}% turnover, ${numTrades} trades required`);
}
console.log();
// 7. Risk decomposition
console.log('7. Risk Decomposition (Max Sharpe Portfolio):');
console.log('-'.repeat(70));
const riskContrib = calculateRiskContribution(maxSharpe, covariance);
console.log(' Asset | Weight | Risk Contrib | Marginal Risk');
console.log('-'.repeat(70));
Object.entries(riskContrib)
.sort((a, b) => b[1].contribution - a[1].contribution)
.forEach(([asset, { weight, contribution, marginal }]) => {
console.log(` ${asset.padEnd(7)} | ${(weight * 100).toFixed(1).padStart(5)}% | ${(contribution * 100).toFixed(1).padStart(11)}% | ${(marginal * 100).toFixed(2).padStart(12)}%`);
});
console.log();
console.log('='.repeat(70));
console.log('Portfolio optimization completed!');
console.log('='.repeat(70));
}
// Generate historical data
function generateHistoricalData(assets, days) {
const prices = {};
const returns = {};
const expectedReturns = {};
const covariance = {};
// Initialize covariance matrix
assets.forEach(a => {
covariance[a] = {};
assets.forEach(b => covariance[a][b] = 0);
});
// Generate correlated returns
for (const asset of assets) {
prices[asset] = [100 + Math.random() * 200];
returns[asset] = [];
// Generate random returns with realistic characteristics
const annualReturn = 0.08 + Math.random() * 0.15; // 8-23% annual return
const dailyReturn = annualReturn / 252;
const dailyVol = (0.15 + Math.random() * 0.25) / Math.sqrt(252);
for (let i = 0; i < days; i++) {
const r = dailyReturn + dailyVol * (Math.random() - 0.5) * 2;
returns[asset].push(r);
prices[asset].push(prices[asset][i] * (1 + r));
}
// Calculate expected return (annualized)
const avgReturn = returns[asset].reduce((a, b) => a + b, 0) / returns[asset].length;
expectedReturns[asset] = avgReturn * 252;
}
// Calculate covariance matrix
for (const a of assets) {
for (const b of assets) {
if (a === b) {
// Variance
const mean = returns[a].reduce((s, r) => s + r, 0) / returns[a].length;
covariance[a][b] = returns[a].reduce((s, r) => s + Math.pow(r - mean, 2), 0) / returns[a].length;
} else {
// Covariance with correlation factor
const meanA = returns[a].reduce((s, r) => s + r, 0) / returns[a].length;
const meanB = returns[b].reduce((s, r) => s + r, 0) / returns[b].length;
let cov = 0;
for (let i = 0; i < days; i++) {
cov += (returns[a][i] - meanA) * (returns[b][i] - meanB);
}
cov /= days;
// Add sector correlation
const sameSecter = sectorMap[a] === sectorMap[b];
const corrFactor = sameSecter ? 1.5 : 0.8;
covariance[a][b] = cov * corrFactor;
}
}
}
return { returns, prices, covariance, expectedReturns };
}
// Equal weight portfolio
function equalWeightPortfolio(assets) {
const weight = 1 / assets.length;
const portfolio = {};
assets.forEach(a => portfolio[a] = weight);
return portfolio;
}
// Minimum variance portfolio (simplified)
function minimumVariancePortfolio(expectedReturns, covariance, constraints) {
const assets = Object.keys(expectedReturns);
const n = assets.length;
// Simple optimization: inversely proportional to variance
const invVariances = assets.map(a => 1 / covariance[a][a]);
const sum = invVariances.reduce((a, b) => a + b, 0);
const portfolio = {};
assets.forEach((a, i) => {
let weight = invVariances[i] / sum;
weight = Math.max(constraints.minWeight, Math.min(constraints.maxWeight, weight));
portfolio[a] = weight;
});
// Normalize to sum to 1
const totalWeight = Object.values(portfolio).reduce((a, b) => a + b, 0);
Object.keys(portfolio).forEach(a => portfolio[a] /= totalWeight);
return portfolio;
}
// Maximum Sharpe ratio portfolio (simplified)
function maximumSharpePortfolio(expectedReturns, covariance, riskFreeRate, constraints) {
const assets = Object.keys(expectedReturns);
// Simple optimization: proportional to excess return / variance
const scores = assets.map(a => {
const excessReturn = expectedReturns[a] - riskFreeRate;
const vol = Math.sqrt(covariance[a][a]) * Math.sqrt(252);
return Math.max(0, excessReturn / vol);
});
const sum = scores.reduce((a, b) => a + b, 0);
const portfolio = {};
assets.forEach((a, i) => {
let weight = sum > 0 ? scores[i] / sum : 1 / assets.length;
weight = Math.max(constraints.minWeight, Math.min(constraints.maxWeight, weight));
portfolio[a] = weight;
});
// Normalize
const totalWeight = Object.values(portfolio).reduce((a, b) => a + b, 0);
Object.keys(portfolio).forEach(a => portfolio[a] /= totalWeight);
return portfolio;
}
// Risk parity portfolio
function riskParityPortfolio(covariance) {
const assets = Object.keys(covariance);
// Target: equal risk contribution
// Simplified: inversely proportional to volatility
const invVols = assets.map(a => 1 / Math.sqrt(covariance[a][a]));
const sum = invVols.reduce((a, b) => a + b, 0);
const portfolio = {};
assets.forEach((a, i) => portfolio[a] = invVols[i] / sum);
return portfolio;
}
// Black-Litterman portfolio (simplified)
function blackLittermanPortfolio(expectedReturns, covariance, constraints) {
const assets = Object.keys(expectedReturns);
// Views: slight adjustment to expected returns based on "views"
const adjustedReturns = {};
assets.forEach(a => {
// Simulate analyst view adjustment
const viewAdjustment = (Math.random() - 0.5) * 0.02;
adjustedReturns[a] = expectedReturns[a] + viewAdjustment;
});
return maximumSharpePortfolio(adjustedReturns, covariance, portfolioConfig.riskFreeRate, constraints);
}
// Efficient frontier point
function efficientFrontierPoint(expectedReturns, covariance, targetVol, constraints) {
// Simplified: interpolate between min variance and max return
const minVar = minimumVariancePortfolio(expectedReturns, covariance, constraints);
const maxSharpe = maximumSharpePortfolio(expectedReturns, covariance, portfolioConfig.riskFreeRate, constraints);
const minVol = calculatePortfolioVolatility(minVar, covariance);
const maxVol = calculatePortfolioVolatility(maxSharpe, covariance);
const alpha = Math.min(1, Math.max(0, (targetVol - minVol) / (maxVol - minVol)));
const portfolio = {};
Object.keys(minVar).forEach(a => {
portfolio[a] = minVar[a] * (1 - alpha) + maxSharpe[a] * alpha;
});
return portfolio;
}
// Calculate portfolio return
function calculatePortfolioReturn(portfolio, expectedReturns) {
let ret = 0;
for (const [asset, weight] of Object.entries(portfolio)) {
ret += weight * expectedReturns[asset];
}
return ret;
}
// Calculate portfolio volatility
function calculatePortfolioVolatility(portfolio, covariance) {
const assets = Object.keys(portfolio);
let variance = 0;
for (const a of assets) {
for (const b of assets) {
variance += portfolio[a] * portfolio[b] * covariance[a][b] * 252;
}
}
return Math.sqrt(variance);
}
// Calculate risk contribution
function calculateRiskContribution(portfolio, covariance) {
const assets = Object.keys(portfolio);
const totalVol = calculatePortfolioVolatility(portfolio, covariance);
const result = {};
for (const asset of assets) {
// Marginal contribution to risk
let marginal = 0;
for (const b of assets) {
marginal += portfolio[b] * covariance[asset][b] * 252;
}
marginal /= totalVol;
// Total contribution
const contribution = portfolio[asset] * marginal / totalVol;
result[asset] = {
weight: portfolio[asset],
contribution,
marginal
};
}
return result;
}
// Display portfolio summary
function displayPortfolio(name, portfolio, expectedReturns, covariance) {
console.log(`\n ${name}:`);
console.log('-'.repeat(70));
// Sort by weight
const sorted = Object.entries(portfolio).sort((a, b) => b[1] - a[1]);
console.log(' Weights: ' + sorted.slice(0, 5).map(([a, w]) => `${a}:${(w * 100).toFixed(1)}%`).join(', ') + (sorted.length > 5 ? '...' : ''));
const ret = calculatePortfolioReturn(portfolio, expectedReturns);
const vol = calculatePortfolioVolatility(portfolio, covariance);
const sharpe = (ret - portfolioConfig.riskFreeRate) / vol;
console.log(` Expected Return: ${(ret * 100).toFixed(2)}%`);
console.log(` Volatility: ${(vol * 100).toFixed(2)}%`);
console.log(` Sharpe Ratio: ${sharpe.toFixed(2)}`);
}
// Run the example
main().catch(console.error);