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

@ -28,6 +28,14 @@ export interface PercentilePath {
value: number;
}
export interface MonteCarloMilestone {
year: number;
date: string;
p10: number;
p50: number;
p90: number;
}
export interface MonteCarloResponse {
dates: string[];
percentiles: {
@ -40,6 +48,7 @@ export interface MonteCarloResponse {
current_value: number;
expected_value: number;
probability_of_gain: number;
milestones: MonteCarloMilestone[];
insufficient_data: boolean;
}

View file

@ -525,6 +525,36 @@ function MonteCarloTab() {
style={{ width: "100%", height: "360px" }}
/>
</div>
{/* Year-by-year milestone table */}
{data.milestones?.length > 0 && (
<div className="bg-card border border-border rounded-xl overflow-hidden">
<div className="px-5 py-3 border-b border-border">
<p className="text-sm font-semibold">Year-by-Year Projections</p>
<p className="text-xs text-muted-foreground mt-0.5">P10 = pessimistic · P50 = median · P90 = optimistic</p>
</div>
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border bg-secondary/30">
<th className="text-left px-5 py-2 text-xs text-muted-foreground font-medium">Year</th>
<th className="text-right px-5 py-2 text-xs text-destructive font-medium">P10</th>
<th className="text-right px-5 py-2 text-xs text-foreground font-medium">P50</th>
<th className="text-right px-5 py-2 text-xs text-success font-medium">P90</th>
</tr>
</thead>
<tbody>
{data.milestones.map((m, i) => (
<tr key={m.year} className={cn("border-b border-border last:border-0", i % 2 === 0 ? "" : "bg-secondary/10")}>
<td className="px-5 py-2.5 font-medium">Year {m.year}</td>
<td className="px-5 py-2.5 text-right tabular-nums text-destructive">{formatCurrency(m.p10, "GBP")}</td>
<td className="px-5 py-2.5 text-right tabular-nums font-semibold">{formatCurrency(m.p50, "GBP")}</td>
<td className="px-5 py-2.5 text-right tabular-nums text-success">{formatCurrency(m.p90, "GBP")}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</>
)}