-
Notifications
You must be signed in to change notification settings - Fork 294
Expand file tree
/
Copy pathoptimization.js
More file actions
428 lines (343 loc) · 14.2 KB
/
optimization.js
File metadata and controls
428 lines (343 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
/**
* 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);