Skip to content

Add a moving-average trend-timing backtest - #5

Merged
valuecodes merged 1 commit into
mainfrom
feat/trend-timing
Jul 21, 2026
Merged

Add a moving-average trend-timing backtest#5
valuecodes merged 1 commit into
mainfrom
feat/trend-timing

Conversation

@valuecodes

Copy link
Copy Markdown
Owner

What

  • Adds a trend-timing simulation — the fourth strategy in the lab — that
    holds 100% stocks while the S&P is above its moving average and steps aside to
    cash (money-market at the fed funds rate) when it falls below. Answers the
    classic "does market timing beat buy-and-hold?" question none of the existing
    pages cover.
  • New engine src/portfolio_research_lab/timing.py (run_ma_timing
    TimingResult), a TimingConfig Pydantic model, and a 3_Trend_Timing.py
    page. Built as a dedicated path-dependent daily loop (like cash_deploy.py)
    because the Strategy protocol only sets fixed initial weights.
  • Methodologically honest by design: the signal is lagged one bar (decided
    from the previous close, executed at today's close — no look-ahead), a
    hysteresis band damps whipsaws, and an optional per-switch transaction
    cost
    (bps) is modelled — timing is exactly where costs bite.
  • Simple or exponential average, with identical warm-up masking; time_in_market
    and switch-count surfaced in the UI. README features + structure updated.

Over 2000–2025 (200-day SMA, 10 bps): max drawdown −23.1% vs −56.8% for
buy-and-hold, 72.4% time in market — the expected "less return, much less risk"
result.

How to test

uv run poe check
uv run poe dev   # open the "Trend Timing" page; try a 200-day window, a band, and a cost

Security review

No security-impacting changes.

Copilot AI review requested due to automatic review settings July 21, 2026 14:57

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 fourth strategy to Portfolio Research Lab: a moving-average trend-timing backtest that switches between 100% stocks and 100% cash using a one-bar-lagged MA signal, optional hysteresis band, and per-switch transaction cost, plus a Streamlit page to explore and compare it against baseline portfolios.

Changes:

  • Introduces a new path-dependent timing engine (run_ma_timing) returning a TimingResult with equity/position series and a switch-event log.
  • Adds a TimingConfig Pydantic model (incl. MA kind validation, band, and transaction cost parameters) and exports the new API from the package root.
  • Adds a dedicated Streamlit “Trend Timing” page and comprehensive unit tests covering lag, band behavior, costs, warm-up masking, and input validation.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/test_timing.py Adds unit tests validating timing engine correctness (lag, switching, costs, warm-up, schema, and error cases).
src/portfolio_research_lab/timing.py Implements the moving-average trend-timing simulation loop and result container.
src/portfolio_research_lab/models.py Adds TimingConfig and MA kind validation constants.
src/portfolio_research_lab/init.py Exposes TimingConfig, TimingResult, and run_ma_timing in the public API.
README.md Updates features + repository structure to include the new strategy and page.
app/pages/3_Trend_Timing.py Adds the Streamlit UI page for running and comparing the trend-timing backtest.

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

@valuecodes
valuecodes merged commit 9dfb348 into main Jul 21, 2026
9 checks passed
@valuecodes
valuecodes deleted the feat/trend-timing branch July 21, 2026 15:05
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