ML predictions Phase 5: Monte Carlo contribution fix and milestone table

Fix contribution compounding: monthly contributions are now added to
asset values before each GBM step so they grow with market returns,
rather than being summed as a static lump at each period.

Add year-by-year milestone table below the fan chart showing P10/P50/P90
portfolio values at each annual checkpoint up to the selected horizon.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-04-28 11:02:41 +00:00
parent 514d177b7b
commit 2940b120e0
3 changed files with 56 additions and 3 deletions

View file

@ -100,14 +100,15 @@ def run_monte_carlo(
for sim in range(n_sims):
asset_values = current_values.copy()
for t in range(n_months):
# Add monthly contribution before GBM step so it compounds
if monthly_contribution > 0:
asset_values = asset_values + weights * monthly_contribution
Z = rng.standard_normal(n_assets)
corr_Z = L @ Z
# GBM step for each asset
asset_values = asset_values * np.exp(
(mus - 0.5 * sigmas ** 2) * DT + sigmas * np.sqrt(DT) * corr_Z
)
port_val = float(asset_values.sum()) + monthly_contribution * (t + 1)
portfolio_paths[sim, t] = max(0.0, port_val)
portfolio_paths[sim, t] = max(0.0, float(asset_values.sum()))
# Compute percentile paths
pcts = {
@ -122,6 +123,18 @@ def run_monte_carlo(
prob_gain = float(np.mean(final_values > total_value))
expected_value = float(np.median(final_values))
# Year-by-year milestones (at each 12-month boundary)
milestones = []
for yr in range(1, years + 1):
idx = min(yr * 12 - 1, n_months - 1)
milestones.append({
"year": yr,
"date": future_dates[idx],
"p10": round(float(np.percentile(portfolio_paths[:, idx], 10)), 2),
"p50": round(float(np.percentile(portfolio_paths[:, idx], 50)), 2),
"p90": round(float(np.percentile(portfolio_paths[:, idx], 90)), 2),
})
return {
"dates": future_dates,
"percentiles": {
@ -131,5 +144,6 @@ def run_monte_carlo(
"current_value": round(total_value, 2),
"expected_value": round(expected_value, 2),
"probability_of_gain": round(prob_gain, 3),
"milestones": milestones,
"insufficient_data": False,
}