Skip to content

Commit 5480924

Browse files
committed
optimized.
1 parent d455d12 commit 5480924

File tree

4 files changed

+210
-86
lines changed

4 files changed

+210
-86
lines changed

monte-carlo-projection/all_tests.js

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,52 @@ function testCompoundReturns() {
216216
console.log(` ✓ Compound calculation correct: ${multipleMatch ? 'PASS' : 'FAIL'}`);
217217
}
218218

219-
// Test 1.7: Verify Box-Muller normal distribution
219+
// Test 1.7: Test currency formatting with new abbreviations
220+
function testCurrencyFormatting() {
221+
console.log('\n1.7 Testing Currency Formatting with New Abbreviations:');
222+
223+
// Test values for different magnitudes
224+
const testValues = [
225+
{ value: 1e21, expected: 'Sx', name: 'Sextillion' },
226+
{ value: 5.5e21, expected: 'Sx', name: 'Sextillion' },
227+
{ value: 1e18, expected: 'Qi', name: 'Quintillion' },
228+
{ value: 2.3e18, expected: 'Qi', name: 'Quintillion' },
229+
{ value: 1e15, expected: 'Qa', name: 'Quadrillion' },
230+
{ value: 7.8e15, expected: 'Qa', name: 'Quadrillion' },
231+
{ value: 1e12, expected: 'T', name: 'Trillion' },
232+
{ value: 1e9, expected: 'B', name: 'Billion' },
233+
{ value: 1e6, expected: 'M', name: 'Million' },
234+
{ value: 10000, expected: 'K', name: 'Thousand (>10K)' },
235+
{ value: 5000, expected: 'K', name: 'Thousand (<10K)' },
236+
{ value: 500, expected: '$', name: 'Hundreds' }
237+
];
238+
239+
console.log(' Testing abbreviation thresholds:');
240+
let allPass = true;
241+
242+
testValues.forEach(test => {
243+
// Simple mock of formatCurrency logic for testing
244+
let suffix = '';
245+
if (test.value >= 1e21) suffix = 'Sx';
246+
else if (test.value >= 1e18) suffix = 'Qi';
247+
else if (test.value >= 1e15) suffix = 'Qa';
248+
else if (test.value >= 1e12) suffix = 'T';
249+
else if (test.value >= 1e9) suffix = 'B';
250+
else if (test.value >= 1e6) suffix = 'M';
251+
else if (test.value >= 1e3) suffix = 'K';
252+
else suffix = '$';
253+
254+
const pass = suffix === test.expected;
255+
allPass = allPass && pass;
256+
console.log(` ${test.name}: ${test.value.toExponential(1)}${suffix} ${pass ? '✓' : '✗'}`);
257+
});
258+
259+
console.log(` ✓ Currency formatting with new abbreviations: ${allPass ? 'PASS' : 'FAIL'}`);
260+
}
261+
262+
// Test 1.8: Verify Box-Muller normal distribution
220263
function testBoxMuller() {
221-
console.log('\n1.7 Testing Box-Muller Transform:');
264+
console.log('\n1.8 Testing Box-Muller Transform:');
222265

223266
const samples = 100000;
224267
let sum = 0;
@@ -259,9 +302,9 @@ function testBoxMuller() {
259302
console.log(` ✓ Normal distribution correct: ${normalDistribution ? 'PASS' : 'FAIL'}`);
260303
}
261304

262-
// Test 1.8: Verify Return Rate is within reasonable range
305+
// Test 1.9: Verify Return Rate is within reasonable range
263306
function testReturnRateRange() {
264-
console.log('\n1.8 Testing Return Rate Reasonable Range:');
307+
console.log('\n1.9 Testing Return Rate Reasonable Range:');
265308

266309
const mu = 0.26; // 26% expected return
267310
const sigma = 0.30; // 30% volatility
@@ -342,9 +385,9 @@ function testReturnRateRange() {
342385
console.log(` ✓ Overall Return Rate Range Test: ${allTestsPass ? 'PASS' : 'FAIL'}`);
343386
}
344387

345-
// Test 1.9: Symmetric Distribution Capping Test
388+
// Test 1.10: Symmetric Distribution Capping Test
346389
function testSymmetricCapping() {
347-
console.log('\n1.9 Testing Symmetric Distribution Capping:');
390+
console.log('\n1.10 Testing Symmetric Distribution Capping:');
348391

349392
const mu = 0.3493; // 34.93% expected return
350393
const sigma = 0.6146; // 61.46% volatility
@@ -403,9 +446,9 @@ function testSymmetricCapping() {
403446
console.log(` Result: ${isSymmetric && lowBias ? 'PASS ✓' : 'FAIL ✗'} (symmetric capping with minimal bias)`);
404447
}
405448

406-
// Test 1.10: Zero Withdrawal Depletion Test
449+
// Test 1.11: Zero Withdrawal Depletion Test
407450
function testZeroWithdrawalDepletion() {
408-
console.log('\n1.10 Testing Zero Withdrawal Depletion:');
451+
console.log('\n1.11 Testing Zero Withdrawal Depletion:');
409452

410453
const initial = 10000;
411454
const mu = 0.3493; // 34.93% expected return
@@ -665,6 +708,7 @@ async function runAllTests() {
665708
testWithdrawalLimit();
666709
testPercentiles();
667710
testCompoundReturns();
711+
testCurrencyFormatting();
668712
testBoxMuller();
669713
testReturnRateRange();
670714
testSymmetricCapping();

monte-carlo-projection/index.html

Lines changed: 83 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,59 +6,98 @@
66
<title>Monte Carlo Projection</title>
77
<link rel="stylesheet" href="styles.css">
88
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"></script>
9+
<script>
10+
function switchTab(method) {
11+
// Update tab buttons
12+
const tabButtons = document.querySelectorAll('.tab-button');
13+
tabButtons.forEach(btn => btn.classList.remove('active'));
14+
event.target.classList.add('active');
15+
16+
// Update tab panels
17+
const percentageTab = document.getElementById('percentageTab');
18+
const fixedTab = document.getElementById('fixedTab');
19+
20+
if (method === 'percentage') {
21+
percentageTab.style.display = 'block';
22+
fixedTab.style.display = 'none';
23+
document.getElementById('percentageRadio').checked = true;
24+
} else {
25+
percentageTab.style.display = 'none';
26+
fixedTab.style.display = 'block';
27+
document.getElementById('fixedRadio').checked = true;
28+
}
29+
}
30+
</script>
931
</head>
1032
<body>
1133
<div class="container">
1234
<h1>Monte Carlo Projection</h1>
1335
<div class="subtitle">Investment Projection with 100,000 Simulations</div>
14-
<div class="subtitle" style="font-size: 0.9em; margin-bottom: 20px;">Start Withdrawals After __ Years (e.g., Year 1 = end of first year)</div>
1536

1637
<div class="controls">
17-
<div class="control-group">
18-
<label for="initialInvestment">Initial Investment ($)</label>
19-
<input type="text" id="initialInvestment" value="1,000,000">
20-
</div>
21-
<div class="control-group">
22-
<label for="expectedReturn">Expected Annual Return (%)</label>
23-
<input type="number" id="expectedReturn" value="17" step="0.1" min="0">
24-
</div>
25-
<div class="control-group">
26-
<label for="volatility">Annual Volatility (σ) (%)</label>
27-
<input type="number" id="volatility" value="20" step="0.1" min="0">
28-
</div>
29-
<div class="control-group">
30-
<label for="yearsProjection">Years to Project</label>
31-
<input type="number" id="yearsProjection" value="30" min="1" max="50">
32-
</div>
33-
<div class="control-group">
34-
<label>Withdrawal Method</label>
35-
<div style="display: flex; gap: 20px; margin-top: 5px;">
36-
<label style="display: flex; align-items: center; font-weight: normal;">
37-
<input type="radio" name="withdrawalMethod" value="percentage" checked style="margin-right: 5px;">
38-
Percentage of Portfolio
39-
</label>
40-
<label style="display: flex; align-items: center; font-weight: normal;">
41-
<input type="radio" name="withdrawalMethod" value="fixed" style="margin-right: 5px;">
42-
Fixed Amount with Inflation
43-
</label>
38+
<!-- Investment Parameters Section -->
39+
<h3>Investment Parameters</h3>
40+
<div class="investment-params">
41+
<div class="params-row">
42+
<div class="control-group">
43+
<label for="initialInvestment">Initial Investment ($)</label>
44+
<input type="text" id="initialInvestment" value="1,000,000">
45+
</div>
46+
<div class="control-group">
47+
<label for="expectedReturn">Expected Annual Return (%)</label>
48+
<input type="number" id="expectedReturn" value="17" step="0.1" min="0">
49+
</div>
50+
<div class="control-group">
51+
<label for="volatility">Annual Volatility (σ) (%)</label>
52+
<input type="number" id="volatility" value="20" step="0.1" min="0">
53+
</div>
54+
</div>
55+
<div class="params-row" style="margin-top: 20px;">
56+
<div class="control-group">
57+
<label for="yearsProjection">Years to Project</label>
58+
<input type="number" id="yearsProjection" value="30" min="1" max="50">
59+
</div>
60+
<div class="control-group">
61+
<label for="withdrawalStartYear">Withdraw from the end of __ years</label>
62+
<input type="number" id="withdrawalStartYear" value="6" min="1" max="50" title="Enter the year when withdrawals should start. Year 1 means withdrawals start at the end of the first year.">
63+
<small style="color: #636e72; font-size: 0.8em; margin-top: 2px;">Year 1 = end of first year</small>
64+
</div>
4465
</div>
4566
</div>
46-
<div class="control-group" id="withdrawalRateGroup">
47-
<label for="withdrawalRate">Annual Withdrawal Rate (%)</label>
48-
<input type="number" id="withdrawalRate" value="3" step="0.1" min="0" max="100">
49-
</div>
50-
<div class="control-group" id="fixedWithdrawalGroup" style="display: none;">
51-
<label for="fixedWithdrawalAmount">Starting Annual Withdrawal ($)</label>
52-
<input type="text" id="fixedWithdrawalAmount" value="40,000">
53-
</div>
54-
<div class="control-group" id="inflationRateGroup" style="display: none;">
55-
<label for="inflationRate">Annual Inflation Rate (%)</label>
56-
<input type="number" id="inflationRate" value="2.5" step="0.1" min="0" max="20">
57-
</div>
58-
<div class="control-group">
59-
<label for="withdrawalStartYear">Withdraw from the end of __ years</label>
60-
<input type="number" id="withdrawalStartYear" value="6" min="1" max="50" title="Enter the year when withdrawals should start. Year 1 means withdrawals start at the end of the first year.">
61-
<small style="color: #636e72; font-size: 0.8em; margin-top: 2px;">Year 1 = end of first year</small>
67+
68+
<!-- Withdrawal Method Tabs -->
69+
<div class="withdrawal-section">
70+
<h3>Withdrawal Method</h3>
71+
<div class="tabs">
72+
<button class="tab-button active" onclick="switchTab('percentage')">Percentage of Portfolio</button>
73+
<button class="tab-button" onclick="switchTab('fixed')">Fixed Amount with Inflation</button>
74+
</div>
75+
76+
<!-- Hidden radio buttons for maintaining compatibility -->
77+
<div style="display: none;">
78+
<input type="radio" name="withdrawalMethod" value="percentage" id="percentageRadio" checked>
79+
<input type="radio" name="withdrawalMethod" value="fixed" id="fixedRadio">
80+
</div>
81+
82+
<div class="tab-content">
83+
<div id="percentageTab" class="tab-panel active">
84+
<div class="control-group">
85+
<label for="withdrawalRate">Annual Withdrawal Rate (%)</label>
86+
<input type="number" id="withdrawalRate" value="3" step="0.1" min="0" max="100">
87+
</div>
88+
</div>
89+
90+
<div id="fixedTab" class="tab-panel" style="display: none;">
91+
<div class="control-group">
92+
<label for="fixedWithdrawalAmount">Starting Annual Withdrawal ($)</label>
93+
<input type="text" id="fixedWithdrawalAmount" value="40,000">
94+
</div>
95+
<div class="control-group">
96+
<label for="inflationRate">Annual Inflation Rate (%)</label>
97+
<input type="number" id="inflationRate" value="2.5" step="0.1" min="0" max="20">
98+
</div>
99+
</div>
100+
</div>
62101
</div>
63102
</div>
64103

@@ -88,14 +127,6 @@ <h1>Monte Carlo Projection</h1>
88127
<div class="stat-label">Worst Case (5th percentile)</div>
89128
<div class="stat-value" id="worst">-</div>
90129
</div>
91-
<div class="stat-card">
92-
<div class="stat-label">Maximum Value</div>
93-
<div class="stat-value" id="max">-</div>
94-
</div>
95-
<div class="stat-card">
96-
<div class="stat-label">Minimum Value</div>
97-
<div class="stat-value" id="min">-</div>
98-
</div>
99130
<div class="stat-card">
100131
<div class="stat-label">Withdrawal Strategy</div>
101132
<div class="stat-value" id="annualWithdrawalRate">-</div>

monte-carlo-projection/script.js

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,12 @@ function initializeDOMCache() {
1313
DOM.withdrawalRate = document.getElementById('withdrawalRate');
1414
DOM.fixedWithdrawalAmount = document.getElementById('fixedWithdrawalAmount');
1515
DOM.inflationRate = document.getElementById('inflationRate');
16-
DOM.withdrawalRateGroup = document.getElementById('withdrawalRateGroup');
17-
DOM.fixedWithdrawalGroup = document.getElementById('fixedWithdrawalGroup');
18-
DOM.inflationRateGroup = document.getElementById('inflationRateGroup');
1916
DOM.loading = document.getElementById('loading');
2017
DOM.results = document.getElementById('results');
2118
DOM.median = document.getElementById('median');
2219
DOM.mean = document.getElementById('mean');
2320
DOM.best = document.getElementById('best');
2421
DOM.worst = document.getElementById('worst');
25-
DOM.max = document.getElementById('max');
26-
DOM.min = document.getElementById('min');
2722
DOM.annualWithdrawalRate = document.getElementById('annualWithdrawalRate');
2823
DOM.withdrawalStartYearDisplay = document.getElementById('withdrawalStartYearDisplay');
2924
DOM.depletionRate = document.getElementById('depletionRate');
@@ -156,22 +151,6 @@ window.onload = async function() {
156151
setupNumberFormatting(DOM.initialInvestment);
157152
setupNumberFormatting(DOM.fixedWithdrawalAmount);
158153

159-
// Withdrawal method handlers
160-
const withdrawalMethodRadios = document.getElementsByName('withdrawalMethod');
161-
for (const radio of withdrawalMethodRadios) {
162-
radio.addEventListener('change', function() {
163-
if (this.value === 'percentage') {
164-
DOM.withdrawalRateGroup.style.display = '';
165-
DOM.fixedWithdrawalGroup.style.display = 'none';
166-
DOM.inflationRateGroup.style.display = 'none';
167-
} else if (this.value === 'fixed') {
168-
DOM.withdrawalRateGroup.style.display = 'none';
169-
DOM.fixedWithdrawalGroup.style.display = '';
170-
DOM.inflationRateGroup.style.display = '';
171-
}
172-
});
173-
}
174-
175154
// Withdrawal year validation with debouncing
176155
const validateWithdrawalYear = debounce(function() {
177156
let value = parseInt(DOM.withdrawalStartYear.value);
@@ -458,8 +437,6 @@ function displayResults(results, initialInvestment) {
458437
DOM.mean.textContent = formatCurrency(stats.mean);
459438
DOM.best.textContent = formatCurrency(stats.percentile95);
460439
DOM.worst.textContent = formatCurrency(stats.percentile5);
461-
DOM.max.textContent = formatCurrency(stats.max);
462-
DOM.min.textContent = formatCurrency(stats.min);
463440

464441
// Display withdrawal information
465442
if (results.withdrawalMethod === 'percentage') {

monte-carlo-projection/styles.css

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,85 @@ h1 {
4141
}
4242

4343
.controls {
44+
margin-bottom: 30px;
45+
}
46+
47+
.controls h3 {
48+
margin: 0 0 15px 0;
49+
color: #2d3436;
50+
font-size: 1.2em;
51+
font-weight: 600;
52+
text-align: center;
53+
}
54+
55+
/* Investment Parameters Section */
56+
.investment-params {
57+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
58+
padding: 20px;
59+
border-radius: 15px;
60+
margin-bottom: 20px;
61+
}
62+
63+
.params-row {
4464
display: grid;
4565
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
4666
gap: 20px;
47-
margin-bottom: 30px;
48-
padding: 20px;
67+
}
68+
69+
/* Withdrawal Section with Tabs */
70+
.withdrawal-section {
71+
margin: 20px 0;
72+
}
73+
74+
.tabs {
75+
display: flex;
76+
gap: 0;
77+
margin-bottom: 0;
78+
border-radius: 15px 15px 0 0;
79+
overflow: hidden;
80+
}
81+
82+
.tab-button {
83+
flex: 1;
84+
padding: 15px 24px;
85+
background: #e0e0e0;
86+
border: none;
87+
cursor: pointer;
88+
font-size: 15px;
89+
color: #666;
90+
transition: all 0.3s ease;
91+
font-weight: 500;
92+
}
93+
94+
.tab-button:hover {
95+
background: #d0d0d0;
96+
}
97+
98+
.tab-button.active {
99+
background: linear-gradient(135deg, #667eea, #764ba2);
100+
color: white;
101+
font-weight: 600;
102+
}
103+
104+
.tab-content {
49105
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
50-
border-radius: 15px;
106+
border-radius: 0 0 15px 15px;
107+
padding: 20px;
108+
}
109+
110+
.tab-panel {
111+
animation: fadeIn 0.3s ease;
112+
}
113+
114+
@keyframes fadeIn {
115+
from {
116+
opacity: 0;
117+
transform: translateY(-10px);
118+
}
119+
to {
120+
opacity: 1;
121+
transform: translateY(0);
122+
}
51123
}
52124

53125
.control-group {

0 commit comments

Comments
 (0)