M5-01: Define backtesting architecture and package boundaries - #227
M5-01: Define backtesting architecture and package boundaries#227rustyeddy wants to merge 2 commits into
Conversation
Adds ADR-035 recording the M5 architecture decision reached via #209's design-notes-then-review cycle (mirroring #176/M4-01's own pattern for ADR-005/006): package boundaries for strategy, journal, report, backtest, service/backtest, and cmd/trader/backtest, plus five decisions review tightened before treating the architecture as settled: - backtest reuses pipeline.Pipeline directly, never service/execution (service/execution serves an external caller's transport-neutral shape; backtest is itself an orchestration package at the same tier). - backtest never constructs adapters/broker/sim.Broker internally -- Runner receives broker.Broker and *pipeline.Pipeline as constructor-injected dependencies, mirroring service/execution's own existing shape (issue #186), keeping backtest free of any concrete adapter import. - No-lookahead is a layered invariant: replay guarantees ordering, the scheduler owns visibility timing, and the strategy View owns what's actually accessible -- not concentrated in one package. - report is rendering-only; the transport-neutral result/metrics model lives in backtest itself, never computed inside a renderer package. - Listing resolution is an injected Runner dependency (instrument.Resolver), not embedded in the immutable backtest.Request -- the resolved listing's reproducibility-relevant fields belong in the run manifest (#215) instead. Also updates docs/arch/package-boundaries.org's preliminary package map: adds pipeline and the service/* tier (both introduced in M4 without a corresponding update) and corrects the stale "strategy deferred to M6" note -- strategy is built in M5 (#210). No code in this issue, per its own scope -- architecture/documentation only. go build ./... confirmed clean (no code touched).
There was a problem hiding this comment.
🟡 Changes recommended
ADR-035 contains a couple of incorrect concrete references (test file name and “previously unused” RunID claim) that should be corrected to keep the documentation accurate.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR captures Milestone 5 (M5) backtesting architecture decisions as ADR-035 and updates the architecture documentation to reflect the intended package layering and dependency direction before implementation begins.
Changes:
- Adds ADR-035 documenting M5 backtesting package boundaries, layering, and invariants (pipeline reuse, injected dependencies, no-lookahead layering, metrics vs rendering, etc.).
- Updates the ADR registry (
adr-decisions.org) to include ADR-035 and adds an explanatory note pointing to the standalone ADR file. - Updates
package-boundaries.org’s diagram and narrative to include thepipelineandservice/*tiers and to correct the stalestrategymilestone note.
File summaries
| File | Description |
|---|---|
| docs/arch/package-boundaries.org | Updates the package dependency diagram and narrative to include pipeline, service/*, and M5 package placement. |
| docs/arch/adr-decisions.org | Registers ADR-035 and adds a note explaining it is maintained as a standalone ADR file. |
| docs/arch/adr-035-m5-backtesting-architecture.org | Introduces ADR-035 documenting M5 backtesting architecture decisions and package boundaries. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| =clock/boundary_test.go='s =TestDomainCodeDoesNotCallTimeDirectly= | ||
| (a whole-module AST scan for direct =time.Now=/=NewTimer=/=After=/ | ||
| =Sleep= calls, excluding =clock=/=cmd=/=adapters=). | ||
| - =id.RunID= (an existing, previously unused =id= kind). |
rustyeddy
left a comment
There was a problem hiding this comment.
Architecture review: the substantive M5 decisions are in good shape and accurately incorporate the #209 review: concrete simulator construction stays out of backtest, no-lookahead is layered across replay/scheduler/View, metrics are separated from rendering, listing resolution is injected rather than embedded in the run request, and scheduler policy ownership is constrained appropriately.
I agree with Copilot's two documentation corrections and would fix them before merge:
- ADR-035 cites
clock/boundary_test.goforTestDomainCodeDoesNotCallTimeDirectly; the actual file isclock/arch_test.go. - Describing
id.RunIDas "previously unused" is inaccurate; it already participates in the existing ID/logging infrastructure. It is enough to say thatid.RunIDalready exists and is the appropriate run identity type for M5.
One additional wording correction: the ADR's Consequences section says a future live package is expected in M7. Our current roadmap has OANDA/live brokerage as M6 after the M5/M6 flip. I recommend avoiding a milestone number there entirely (a future live package) so this ADR does not become stale again if roadmap sequencing changes.
With those three documentation fixes, I consider ADR-035/package-boundaries direction ready to merge. No architectural blocker beyond them.
…lestone refs Rusty + Copilot both caught two factual errors and one staleness risk: 1. ADR-035 cited clock/boundary_test.go for TestDomainCodeDoesNotCallTimeDirectly; the actual file is clock/arch_test.go -- verified directly. 2. ADR-035 described id.RunID as "previously unused"; it already has GenerateRunID/ParseRunID support and a logging.RunID attribute -- verified directly. Corrected to describe it as the appropriate existing run identity type for M5, not something new. 3. Both ADR-035's Consequences section and package-boundaries.org cited "M7" for a future live package. Current roadmap has live brokerage at M6 after the M5/M6 flip -- dropped the milestone number entirely from both so this documentation does not go stale again if roadmap sequencing changes.
|
Addressed all three:
|
What changed
Adds ADR-035 recording the M5 backtesting architecture and package boundaries, reached via #209's design-notes-then-review cycle — mirroring the pattern issue #176 (M4-01) used to extract ADR-005/ADR-006 ahead of M4 implementation. Also updates
docs/arch/package-boundaries.org's preliminary package map.Why it changed
Issue #209 asks for M5's architecture to be settled before implementation hardens accidental boundaries — no code, per its own scope.
Package boundaries: new
strategy/,journal/,report/,backtest/,service/backtest/,cmd/trader/backtest/packages, following the exact layering M4 already proved forpipeline/service/execution/cmd/trader/execution.Five decisions review tightened before treating the architecture as settled:
backtestreusespipeline.Pipelinedirectly, neverservice/execution—service/executionserves an external caller's transport-neutral shape;backtestis itself an orchestration package at the same tier.backtestnever constructsadapters/broker/sim.Brokerinternally —Runnerreceivesbroker.Brokerand*pipeline.Pipelineas constructor-injected dependencies, mirroringservice/execution's own existing shape (issue M4-11: Add execution and risk application service #186), sobackteststays free of any concrete adapter import (direct application ofpackage-boundaries.orgrule 1).Viewowns what's actually accessible.reportis rendering-only; the transport-neutral result/metrics model lives inbacktestitself — a presentation package must not become the owner of financial/statistical computation.Runnerdependency (instrument.Resolver), not embedded in the immutablebacktest.Request— a resolver is runtime infrastructure, not run input. The resolved listing's reproducibility-relevant fields belong in the run manifest (M5-07: Define immutable backtest run manifest #215) instead.Also confirmed and recorded: v0 targets one account/one broker (portfolio aggregation deferred to #224), and the scheduler is not a policy owner (strategy emits intent, pipeline owns execution/risk semantics, broker owns authoritative state, journal observes, metrics derive from authoritative outcomes after the fact).
package-boundaries.orgupdate: addspipelineand theservice/*tier to the dependency diagram (both introduced in M4 without a corresponding doc update) and corrects a stale "strategydeferred to M6" note —strategyis built in M5 (#210).How it was tested
Documentation-only change.
go build ./...confirmed clean (no code touched). Every existing-code reference in the new ADR (clock.Simulated.Advance,marketdata.Manager.Bars/BarQuery,id.RunID,clock/boundary_test.go'sTestDomainCodeDoesNotCallTimeDirectly) was verified directly against the actual source before citing it.Which documentation changed
This PR is the documentation change — see above.
Part of #209.