Realistic broker on the position-event table + Broker/Account composite (Stage 2) #116
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Reframed from 'single-pass broker on exposure (Architecture A)' per #117. Stage-2 scope: a realistic broker node consuming the derived position-event table (real spread / commission / slippage / lot / margin), emitting a currency equity stream; plus a Broker/Account composite aggregating per-symbol P&L into account equity, with the equity->Sizer feedback cut by a z^-1 register on the fill edge (compounding). Account mode (netting/hedging) = composition constraint.
RealisticBroker node: single-pass on exposure+price, currency equity (Architecture A)to Realistic broker on the position-event table + Broker/Account composite (Stage 2)/bossrun — reference issue for the Realistic-broker (C10 A-side, Stage 2) milestone. Fork decisions for this run are logged here.Ordering decision: broker milestone before #147 (domain-agnostic results substrate)
At the close of #136 the registry was deliberately kept domain-aware, with the
RunReport<M>/ domain-agnostic-World abstraction deferred to #147. The open sequencing fork was: do #147 (abstract the registry metric/deflation vocabulary) first, or this broker milestone first?Decided: this broker milestone first; #147 after. A sound abstraction of the metric vocabulary needs ≥2 concrete metric families to generalize over. Today there is only one (R-metrics, from the Stage-1 work). This milestone introduces the second — currency metrics +
net_currency_pnl(#121). Abstracting before that second family exists means generalizing from a single example: premature, and likely to encode R-specific shape as if it were general. The #136-close deferral ("registry stays domain-aware by design") was exactly that signal. So #147 builds on the broker, not the reverse → broker first.Provenance: the user delegated this sequencing call to the orchestrator and asked it to proceed.
Cycle-1 scope (orchestrator decision)
The milestone spans several cycles. Cycle 1 = the
RealisticBrokernode + its friction model (#120), feed-forward only. A streaming broker node that produces a currency-denominated equity curve for one symbol (marking the sized position — volume from the Sizer'ssize, carried in the PositionManagement record — distinct from the existingSimBroker, which marks the unsized exposure to a pip curve). Frictions: spread + commission as authored, tunable, overridable params (sweepable per C12); slippage a declared no-op hook; authored cost values win over any recorded per-bar spread.Explicitly deferred to later cycles: registry currency metrics +
net_currency_pnlranking (#121); persistence of the currency-equity tap + dup-ts-safe position-event table (#122); CLIaura run --realattaching both brokers (#123); the Broker/Account composite + equity→Sizer compounding feedback with thez⁻¹register on the fill edge (later in #116). Cycle 1 stays flat-1R feed-forward — no equity feedback.A load-bearing design fork for cycle 1 — the broker's input model (stream the PM record vs. consume the derived event table, which can hold >1 row per timestamp against C8) — is handed to
specifyto derive and record here.Design reconciliation (specify) — cycle 0081, spec
0081-realistic-broker-nodeResolving the load-bearing input-model fork for the
RealisticBrokerbefore writing the spec.Fork: how does the RealisticBroker consume the executed book? C10's prose says the broker "consumes the derived position-event table" / sits "on the derived position-event table." But that table is a post-run computed artifact that can hold >1 row per
event_ts(a reversal = Close + opposite open), so a streaming node (C8: ≤1 record pereval) cannot take it as a per-evalinput stream. Two readings: (A) the broker is a post-run fold over the table (sibling ofderive_position_events); (B) the broker is an in-graph streaming node reading the executor's per-evaloutput (thePositionManagementdense record) + price, with the position-event table kept as the decoupled audit layer.→ Decided: (B), an in-graph streaming node. Basis: derived —
SimBroker(pip).currency_equityas "a normal ColumnarTrace tap … existingpersist_tracespath" — a tap is the recorder-sink output of an in-graph node (exactly likeSimBroker'sequitytap), not a post-run series.evalinput to be thePositionManagementrecord (≤1 record/eval), since the position-event table (>1 row/instant) is structurally ineligible as a per-evalstream. The record carries the same fills the table materializes (direction,size,open, prices), so the broker reads the executor's per-evaloutput and the table remains the decoupled, derived audit sibling (C10 "decoupled, derived"; persisted separately per #122).C10 clarification (not a break). "The table feeds the realistic broker nodes" is read as conceptual — the broker operates on the executed book, whose per-
evalform is the executor's record and whose materialized form is the table. The mechanical streaming input is the record; the table is the audit layer. The R-evaluator (summarize_r, post-run) vs. realistic broker (in-graph) split is preserved in spirit; the ledger note will be synced at cycle close if the wording needs tightening.Reviewable + vetoable here and via the spec's auto-sign notify.
Reconsideration → bounce (specify, cycle 0081 — spec draft discarded)
Before committing the spec I pulled two
spec-skepticbias-breakers on it (the fork was foundational + cascading). Both returned BLOCK; both are correct.scope-fork (architecture): BLOCK. The in-graph-node decision (B, recorded above) contradicts the C10 contract, which says — explicitly, in deliberate contrast to the R-evaluator — "realistic brokers on the derived position-event table," "this table feeds the realistic broker nodes," "a realistic broker node consumes the events." That is reading (A): a post-run fold over the position-event table (sibling of
derive_position_events), not an in-graph node. My (B) signals were weak: "ordinary downstream nodes (C8/C9)" also namessummarize_r, which IS a post-run fold — so "node" ≠ in-graph; "C8 forces the PM record" is circular (C8 bars the table only as a per-evalstream; a post-run fold over it is C8-clean and matches C10). Only #122's "tap" leans (B), and that is an open issue, not a contract. Shipping (B) would break a contract and amend the ledger after the fact — backwards (CLAUDE.md: amend the contract first).ambiguity (currency model): BLOCK — a 1e5× unit error. The Sizer's
size = risk_budget / stop_distance(stop_distance in price units) is dimensionally a base-unit position (its stop-loss =risk_budgetcurrency), not lots. The spec callssizeboth "lots" and "a base-unit position," then multiplies bylot_size— double-counting. The two readings diverge bylot_size; the frictionless-parity acceptance inherits the undefined unit.Deeper concern. C10 ties currency P&L to the compounding Stage 2 (fixed-fractional sizing that reads equity, with the z⁻¹ fill-edge register). A feed-forward currency curve over flat-1R sizing is ≈ the R-curve scaled by
risk_budget— so the cycle-1 scoping ("feed-forward currency broker first, defer the feedback") may itself be incoherent with how C10 defines currency.Conclusion. The broker milestone's first cycle is not settled enough for
specify. Three unresolved load-bearing forks: (1) architecture A (post-run fold, contract-faithful) vs B (in-graph node, matches #122's "tap") — and the C10/#122 wording reconciliation either way; (2) the currency unit/volume model; (3) whether a feed-forward currency cycle is coherent at all vs. C10's compounding-tied currency. This needsbrainstorm(discovery) — a new cycle — so it is bounced to the user. Spec draft discarded (was uncommitted).Milestone retired — C10 reworked (run close)
The realistic-broker model this milestone was built on is retired. The design discussion in this thread concluded that modelling real broker friction (slippage, swaps, spread) over historical data is horseshoe-throwing — real friction is not historically knowable; the only reliable friction is measured forward against a real broker. C10 was reworked accordingly and committed (
29cdc8c):round_trip_cost; the headline artifact is the net-R curve (net_r_equity). No money in research.z⁻¹fill-edge register and the flat-1R-vs-compounding structural axis are gone. Executor = stop-rule → position-management in R. Conviction-based risk allocation survives as an R-aggregation axis.Re-scope:
Closing this issue.