Skip to content

Add a point-in-time optimal cash reserve page - #6

Merged
valuecodes merged 3 commits into
mainfrom
feat/reserve-over-time
Jul 21, 2026
Merged

Add a point-in-time optimal cash reserve page#6
valuecodes merged 3 commits into
mainfrom
feat/reserve-over-time

Conversation

@valuecodes

Copy link
Copy Markdown
Owner

What

  • The Optimizer page fits one static cash reserve over the whole window. This adds a complementary view: how the best reserve % drifts through history — useful for seeing whether "hold more dry powder" ever actually paid off, and when.
  • New page app/pages/3_Reserve_Over_Time.py: at a series of expanding-window snapshots, it uses only the price history up to each date (no look-ahead), holds the deploy rule + refill fixed, and sweeps the cash reserve to find the one that maximised the chosen objective. It is an in-sample fit at each date (clearly flagged), not an out-of-sample claim.
  • New backend optimal_reserve_over_time() in optimizer.py (plus ReserveSweepPoint / ReserveSweepResult), reusing the existing objective + cash-deploy backtest machinery. Deterministic — no sampler, no seed.
  • Visuals: optimal-reserve-over-time line with the index drawdown on a secondary axis, a full objective heatmap (reserve by snapshot date), and a snapshot table. Snapshots are anchored by row/calendar so they stay stable as new data arrives.

Note on behaviour: under the return-only objectives (Excess CAGR / CAGR), the optimal reserve sits at the grid floor (5%) at every snapshot — holding cash only drags a long-rising, price-return-only series. The reserve trade-off appears under the Sharpe and drawdown-capped objectives. This is expected, not a bug.

How to test

uv run poe check
uv run poe dev

Then open Reserve Over Time, press Run sweep, and confirm the line + heatmap render and the headline metric matches the latest snapshot. Switch the objective to Sharpe (vs cash) or Excess CAGR, drawdown-capped to see the reserve vary across time.

Security review

No security-impacting changes.

Copilot AI review requested due to automatic review settings July 21, 2026 15:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “Reserve Over Time” workflow to complement the existing Optimizer by computing (in-sample, no-lookahead) the point-in-time optimal cash reserve across expanding-window snapshots, with a Streamlit UI for visualization.

Changes:

  • Added optimal_reserve_over_time() plus ReserveSweepPoint/ReserveSweepResult to sweep reserve % over expanding windows deterministically.
  • Introduced a new Streamlit page (Reserve Over Time) with reserve trajectory + objective heatmap + snapshot table.
  • Added test coverage validating shape, determinism, tie-breaking, no-lookahead windowing, and input validation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
tests/test_optimize.py Adds unit tests for the new reserve-over-time sweep behavior and contracts.
src/portfolio_research_lab/optimizer.py Implements the expanding-window reserve sweep API and related dataclasses/utilities.
src/portfolio_research_lab/init.py Re-exports the new sweep API and result types from the package top-level.
app/pages/3_Reserve_Over_Time.py Adds a new Streamlit page to run and visualize the reserve sweep over time.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/portfolio_research_lab/optimizer.py Outdated
Comment thread src/portfolio_research_lab/optimizer.py
)
min_rows = warmup_years * base.trading_days_per_year

years = prices.index.map(lambda ts: ts.year)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept .map here: the vectorized prices.index.year fails this repo's ty gate (.index is typed as the generic Index, which has no .year attribute), so it needs a type-ignore to compile. This mirrors 2_Optimize.py; added a comment noting why.

@valuecodes
valuecodes merged commit 61d73e4 into main Jul 21, 2026
8 checks passed
@valuecodes
valuecodes deleted the feat/reserve-over-time branch July 21, 2026 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants