Skip to content

feat(strategies): add RunReport with serialization and warm-start - #1005

Merged
collinsezedike merged 4 commits into
drydocs:mainfrom
pimaster900:feat/run-report
Oct 9, 2026
Merged

collinsezedike merged 4 commits into
drydocs:mainfrom
pimaster900:feat/run-report

Conversation

@pimaster900

@pimaster900 pimaster900 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add RunReport, a self-contained record of a backtest run carrying the scenario it was produced from, the risk metrics, the event counts and the final portfolio state.
  • Make RunReport.scenario the canonical Scenario rather than a four-field summary, so the report carries everything a run is reproduced from. The seed, the data sources, the starting capital and the strategy params were previously not recorded at all, so two runs differing in any of them produced indistinguishable reports. engineVersion stays its own field, since strategy.version versions the strategy config and not the engine.
  • Validate the embedded scenario through parseScenario on deserialize, so a corrupted report cannot hand a caller a scenario the engine would refuse at run time.
  • Serialize every FixedPointDecimal as its raw stroop count in a decimal string, so a report round-trips through JSON without value drift, and reject a malformed stroop value rather than letting BigInt read it as a number. A JSON number is rejected as well, since RegExp.test coerces and JSON.parse has already rounded the value by the time the guard sees it.
  • Version the format through REPORT_FORMAT_VERSION and fail loudly on an unknown version with UnknownReportVersionError instead of misparsing it.
  • Add buildWarmStartContext and mergeReports so a long run resumes from a saved report rather than replaying from the beginning. The context carries a full continuation scenario, and a window that is not a whole number of steps is rejected rather than handed to a clock that would throw on it.
  • Make mergeReports reject a continuation that does not continue the base. It checks that the window start is the base's final timestamp, that the starting capital is the base's final value, that the window end is the requested end, that the final state is from the continuation's own window end rather than earlier, that the engine version matches, and that the seed, source, strategy, params, assets and step agree. Window boundaries are compared as instants rather than as strings.
  • Keep sub-second instants intact when a window boundary is rendered back to a string. The scenario schema and the run clock both hold milliseconds, so truncating them moved a continuation's start off the state it continues and made mergeReports reject a valid pair.
  • Move the ISO-8601 window helpers into scenario.ts, next to the duration format they implement, so the report does not reach into a market-regime fixture builder for them.
  • Warm-start equivalence is asserted for a deterministic strategy, since the report does not carry generator state. The remaining gap for path-driven strategies is filed as [Feature] Persist generator state so a resumed run continues its stream #1018.
  • Export the report types and their serializers from the package index.

Test plan

  • pnpm --filter @meridian/strategies coverage passes, 656 tests across 35 files
  • report.ts at 100% across statements, branches, functions and lines
  • pnpm typecheck, pnpm typecheck:api and pnpm lint pass across the workspace
  • npx prettier --check passes on the changed files
  • Removing any one merge guard (continuation start, starting capital, window end, final state, engine version, seed, source, strategy id or version, params, assets, window step), the whole-step window check, the embedded-scenario validation, the stroop validation, the non-string stroop check, the millisecond handling or the merged-drawdown selection each fails the matching test

Closes #882

@vercel

vercel Bot commented Oct 4, 2026

Copy link
Copy Markdown

@pimaster900 is attempting to deploy a commit to the Collins' projects Team on Vercel.

A member of the Team first needs to authorize it.

pimaster900 and others added 2 commits October 9, 2026 16:11
- Define RunReport type (scenario, risk metrics, event counts, final state)
- Serialize/deserialize with stroops-as-strings for lossless round-trips
- Version the format; unknown versions throw UnknownReportVersionError
- Add buildWarmStartContext and mergeReports for resuming long runs
- Export all new types and functions from package index
- 25 tests covering round-trip fidelity, warm-start equivalence, and version errors

@collinsezedike collinsezedike left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@pimaster900 thank you for this contribution. A run report that carries the canonical Scenario closes a real gap, since a run's inputs were previously not recoverable from its output at all, and the warm-start path gives that record a second use.

I pushed a commit to your branch with some tightening on top: mergeReports now also rejects a continuation whose starting capital disagrees with the base's final value and one whose final state predates its own window end, deserializeFixed requires a string so a JSON number cannot reach BigInt already rounded, and sub-second instants survive the round trip through a window boundary.

Merging now.

@collinsezedike
collinsezedike merged commit 391f6fe into drydocs:main Oct 9, 2026
9 of 10 checks passed
@collinsezedike

Copy link
Copy Markdown
Collaborator

@pimaster900 if Meridian is useful to you, a star on the repository would help other developers find it.

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.

[SDK] Add the run-report format and warm-start

2 participants