Skip to content

Monte Carlo Simulation & Goal Attainment Probability Module #319

Description

@robertocarlous

Problem Statement

A savings goal (src/goals/service.ts) tells a user "you need X% APY to hit your target by date D" — a single deterministic line. Real returns are random, and the platform already holds years of ProtocolRate history and YieldSnapshot series it could use to answer the question the line hides: what is the actual probability of hitting this goal? A "required APY" that no realistic sequence achieves is a trap the user walks into with real money. This issue adds a Monte Carlo simulation module that turns goal feasibility and backtest results into probability distributions and confidence intervals, with the same rigor (and the same honest-failure discipline) as the rest of the analytics stack.

Current State

  • src/goals/service.ts computes required-rate projections using a simple non-compounding APY convention shared with src/agent/snapshotter.ts and src/agent/strategyMetrics.ts. src/agent/backtest.ts is a deterministic historical replay — one path, one answer, with forward-filled daily rate series and a documented gap policy.
  • ProtocolRate carries the historical APY/TVL record (with rawResponse from the collector); YieldSnapshot carries per-position value history (retained ~90 days). src/agent/riskScoring.ts already computes per-protocol volatility/trend factors — precedent for deriving distribution parameters from history.
  • The backtest sandbox (src/routes/backtest.ts) exposes runBacktest results (final value, max drawdown, realized APY) with no uncertainty banding.

Proposed Solution

1. Pure simulation core (src/analytics/montecarlo.ts or src/agent/)

A zero-I/O module, unit-tested and deterministic under a seed:

  • Sampling model — two documented modes:
    • Historical bootstrap: resample from the actual period-return series (with the same <= 0 starting-value skip and gap-robust annualization rules as src/agent/strategyMetrics.ts), so no distributional assumption is imposed.
    • Parametric: fit mean/σ to the period returns and draw from a lognormal/Student-t model (choice documented; the codebase's "state the assumption" discipline from riskFreeRate applies).
  • Engine: simulate the same decision loop as runBacktest (per-day APY accrual, rebalance decisions per RebalanceStrategy.analyze, identical BigInt wei-amount handling and simple-rate conventions) — reusing the existing BacktestRequest/StrategyParams shapes so a Monte Carlo run is a distribution of backtests, not a parallel implementation.
  • Outputs: per-path terminal values; mean/median/percentile bands (5/50/95) on terminal value and drawdown; probability of achieving a target amount by a target date (the goal question); convergence diagnostics (recommended iteration count, optional seed for reproducibility). Report iterations, effectiveSampleSize if computable, and a converged flag — never present a noisy 1,000-path answer as if it were a fact.

2. Goal integration (src/goals/service.ts, src/routes/goals.ts)

  • New endpoint POST /api/v1/goals/:id/simulate (owner-scoped): given the goal's targetAmount, targetDate, startingAmount, and the user's effective strategy/allocation (same effective-config resolution as src/agent/effectiveStrategy.ts, including any active follow and the goal's own riskCeiling), returns:
    • attainmentProbability (fraction of paths that crossed the target by the target date),
    • the median and 5/95 percentile projected balances at the target date,
    • required-rate sensitivity (a short table: "at X% you have Y% chance" across the goal's feasible rate range) — so the user can see how much margin the target actually has.
  • Validation: target date must be in the future; insufficient history must return an explicit insufficient_history outcome (same shape as runBacktest's), not a guessed probability.

3. Backtest integration (src/routes/backtest.ts)

  • Extend the backtest sandbox with an optional ?simulate=true&iterations=N&seed=S that returns the distribution plus the deterministic path overlaid, so the sandbox's headline number stops being a point estimate.
  • Backward compatible: no simulate param → identical output to today (regression tests).

4. Caching & performance

  • Simulations are CPU-heavy and identical inputs are common. Cache results keyed by a canonical hash of the effective inputs (config, seed, rate-series window, iterations) using the Redis layer (src/config/redis.ts); TTL bounded and configurable. The cache must never serve a stale-parameter answer — the key must include every input that changes the distribution.
  • Cap iterations (configurable max) to bound CPU and protect the sandbox from abuse; document the throughput budget in the PR.

5. Documentation & correctness disclosure

  • Update docs/ (and the goal/backtest docs) to state plainly: simulation assumes historical regimes persist; it is not a forecast or a promise; output is a distribution, not a guarantee. The response payload itself must carry this disclaimer (isSimulation: true, model), following the "stated assumption beats hidden default" precedent.

Edge Cases & Failure Modes

  • Zero/negative start: skip-guard as in the rest of the analytics stack; startingAmount <= 0 is a 400.
  • Empty or gap-heavy history: forward-fill policy inherited from buildDailyRateSeries; if the first observation is after the requested start, return insufficient_history with earliestAvailableDate.
  • All paths identical (degenerate series): report zero variance honestly, and converged: false — do not invent spread.
  • Non-convergence at N iterations: surface converged: false and recommend more iterations rather than hiding it.
  • Concurrent simulations for the same user: the cache key + per-user request limits must prevent a burst of identical sims from pinning the process; consider a simple per-user concurrency bound (a semaphore per key), with an explicit simulation_in_progress (or 429-style) response.
  • Goal achieved mid-simulation: a path that crosses the target early still counts as achieved (and stops accruing), matching what a real investor would do.

Security & Privacy Considerations

  • Simulations are owner-scoped; the effective-config resolver must not leak a followed publisher's config details into the response beyond what the goal endpoint already exposes.
  • The simulation module stays pure (no stellar/db imports), enforced by the same import-graph test pattern as src/agent/backtest.ts — a simulation must never be able to touch real funds.
  • CPU abuse: rate limits on the simulate endpoints, per-user sim concurrency bound, and documented iteration caps.

Out of Scope

  • Portfolio optimization (choosing weights) — this module only evaluates probability given a config (see the separate optimization issue if desired).
  • Correlated multi-asset sampling beyond what the protocol series already imply (no covariance model in v1; document).
  • ML regime models.

Suggested Implementation Plan

  1. Pure montecarlo.ts core (bootstrap + parametric modes, seeded, determinism tests, degenerate cases).
  2. Reuse runBacktest decision loop per path; verify distribution-of-backtests vs. point-backtest consistency with regression tests.
  3. Goal simulate endpoint + validation + disclaimer.
  4. Backtest sandbox simulate extension (backward compatible).
  5. Redis caching + concurrency bounds + docs.

Acceptance Criteria

  • Seeded, deterministic Monte Carlo with bootstrap and parametric modes; a fixed seed reproduces byte-identical output (test)
  • Outputs: percentile bands, median, drawdown distribution, attainmentProbability, convergence flag; degenerate/non-converged cases reported honestly
  • POST /api/v1/goals/:id/simulate returns probability + sensitivity table + explicit isSimulation disclaimer; insufficient_history handled
  • Backtest sandbox simulate extension is fully backward compatible (regression tests)
  • Redis cache keyed on canonical input hash; per-user concurrency bound + iteration cap
  • Pure-module import-graph test (no stellar/db imports in the core)
  • docs/openapi.yaml updated; unit + integration tests green

Metadata

Metadata

Assignees

No one assigned

    Labels

    GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26Third CampaignCampaign: Third CampaignenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions