Skip to content

feat: point-in-time index membership for replays, and --end for a past year on its own - #107

Merged
yanivil merged 1 commit into
mainfrom
feat/point-in-time-universe
Sep 8, 2026
Merged

yanivil merged 1 commit into
mainfrom
feat/point-in-time-universe

Conversation

@yanivil

@yanivil yanivil commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Description

Implements #95.

  • tools/universe_history.py. Membership as of any past date from the git history of datasets/s-and-p-500-companies, the dataset the scanner already pins: a bare, blobless clone under .cache/ (under 1 MB, one second, refreshed by a fetch on later runs; a failed refresh serves the cache), the file at the last commit on or before the date, parsed by the new scan.symbols_from_csv so snapshots are normalised exactly like today's list. Dates before the first commit get the first snapshot and are counted. The 2016 snapshot holds 179 symbols that are not members today.
  • Backtest --constituents-asof (workflow input asof): monthly snapshots over the window give the union to download; walk_forward takes a members callable and scans a symbol only on the days it was a member. The report opens with a per-year coverage table: members and how many have Yahoo history. That gap is the survivorship bias that remains, and a transient Yahoo miss counts the same way, which the wiki says.
  • --end YYYY-MM-DD (input end): the --days sessions on or before a date, so one past year is one dispatch. scan_sessions is the shared window logic.
  • Workflow input grid (default true) so a long single-profile replay skips the variants and the other profile's pass; job timeout 360 minutes. .cache/ ignored.
  • Wiki 04 (how it works, its two limits, the per-year dispatch), wiki 01 universe section, README layout and limitations, changelog.

First use, locally, 2022 on the index as it was (--constituents-asof --period 10y --end 2022-12-30 --days 250 --bars 500 --profile tuned; 531 members, 481 with history, 10 minutes):

Slice Signals Hit rate Mean R 95 % CI, month blocks Max DD
all 175 29 % +0.03 [−0.50, +0.43] −38 R
Inverse H&S 131 27 % −0.14 [−0.60, +0.22] −30 R
Cup & Handle 14 10 % −0.34 −8 R
Bullish Wolfe Wave 30 44 % +0.94 [−0.30, +1.61] −5 R

The bear year the two-year replay never contained: expectancy near zero, inverse H&S negative, Wolfe the only positive detector. On this window the breakout-age (#98) and SMA-band (#99) splits show no effect, VIX above 25 was again the best bucket (#101), and 63 % of signals hit the stop within 60 bars. Not written into the wiki tables: the protocol wants a GitHub run for that, one dispatch per year.

Verified: ruff clean, 134 tests pass (6 new: the commit list of a temporary dataset repository, the snapshot in force including the commit day and dates before history, Yahoo-format symbols, union and coverage, the cached clone and its refresh, walk_forward members and --end, symbols_from_csv).

Related Issues

Closes #95
Related: #96 (protocol), #98, #99, #101, #102 (hypotheses that now have a third window)

Checklist

  • Code follows existing project style and type annotations
  • Threshold changes include a one-line "why" comment in scan.py (no thresholds changed)
  • Offline unit tests pass (python -m pytest -q)
  • Random-walk false-positive rate stays under 5% (detection logic untouched)
  • Documentation updated in docs/wiki/
  • CHANGELOG.md updated

🤖 Generated with Claude Code

…t year on its own

tools/universe_history.py reconstructs the S&P 500 membership as of any date
from the git history of the pinned constituent dataset: a bare, blobless
clone under .cache/, the snapshot at the last commit on or before the date,
symbols normalised like today's list through the new scan.symbols_from_csv.

The backtest's --constituents-asof downloads the union of members over the
window and scans a symbol only on the days it was a member; the report opens
with a per-year coverage table (members, members with Yahoo history), which
is the survivorship bias that remains. --end YYYY-MM-DD replays the sessions
on or before a date so a past year is one dispatch. Workflow inputs asof, end
and grid (grid=false skips the variants and the other profile's pass); job
timeout 360 minutes.

First use, 2022 on the index as it was (531 members, 481 with history): the
tuned profile at +0.03 R on 175 signals, drawdown -38 R.

Closes #95.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@yanivil
yanivil merged commit fc1b73f into main Sep 8, 2026
5 of 6 checks passed
@yanivil
yanivil deleted the feat/point-in-time-universe branch September 8, 2026 15:45
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.

Point-in-time constituents for multi-year replays

1 participant