Skip to content

docs: account risk and capital management primer - #419

Merged
rustyeddy merged 5 commits into
mainfrom
docs/418-account-risk-primer
Sep 27, 2026
Merged

rustyeddy merged 5 commits into
mainfrom
docs/418-account-risk-primer

Conversation

@rustyeddy

Copy link
Copy Markdown
Owner

What changed

Adds docs/account-risk.md, a primer and roadmap for account-level risk:

  • vocabulary
  • the layered risk model mapped onto Trader's real pipeline (Sizer → Planner → risk.Engine → broker → journal)
  • margin rules by market type
  • parameters
  • invariant tests

Every item is marked as implemented [today], planned in the Account Margin Admission milestone ([#410]–[#417]), or [future].

Why

The draft of this document was written against a different codebase:

  • It referenced RunPortfolio, portfolio_runner.go, and AccountManager, none of which exist here.
  • It misdiagnosed the 4.6x SPY backtest.
  • It proposed an API that conflicts with ADR-004 (float64), ADR-006/ADR-029 (a RiskManager that resizes), and the separation of live guards from trade risk.

This version keeps the useful general material and corrects everything specific to Trader. The real cause of the 4.6x run: an unbounded fixed-fraction sizer, risk.NewEngine() composed with no rules in cmd/trader/backtest, and a simulator with no margin policy.

How it was tested

Documentation only. I checked every referenced type, rule, file, and ADR against the current tree.

Documentation changed

Closes #418

🤖 Generated with Claude Code

Add docs/account-risk.md: vocabulary, a layered risk model, market-type
margin rules, and invariant tests for account-level risk. Each layer is
mapped to what Trader implements today, what the Account Margin
Admission milestone (#409, #410-#417) adds, and what remains future
work.

The primer records the real cause of the 4.6x SPY backtest: an
unbounded fixed-fraction sizer, an empty risk engine in the backtest
composition root, and a simulator with no margin policy. It follows
the accepted ADRs: exact numerics (ADR-004), sizing kept separate from
admission (ADR-006), strict approve/reject with no resizing (ADR-029),
and live guards kept separate from trade risk.

Closes #418

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 25, 2026 23:32

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.

Copilot review overview

🟡 Changes recommended

The documentation contains multiple inaccuracies and ambiguous status or milestone statements that should be corrected before approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 6 Low severity

Open (6)
What changed in this PR

Adds a Trader-specific account risk and capital management primer and roadmap.

Changes:

  • Documents the sizing, risk, broker, and journal pipeline.
  • Explains margin concepts, parameters, and milestone work.
  • Defines proposed invariant tests and corrects prior architecture assumptions.
File Description
docs/​account-risk.md Adds the account risk primer and roadmap.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/account-risk.md Outdated
|---|---|---|
| **Equity / NAV** | Cash + unrealized P&L of open positions. The base for all percentages. | `account.Snapshot.Equity()` |
| **Balance** | Cash only. Don't size off this when positions are open. | `Snapshot.CashBalances()` |
| **Notional (exposure)** | Units × price × multiplier, in account currency. | computed by rules from `Input.ReferencePrice` |

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 1c70be6: the row now says per-position rules value the proposal at Input.ReferencePrice, and the account-wide aggregate values other open positions at their current marks (#411, #412).

Comment thread docs/account-risk.md Outdated
| **Gross exposure** | Σ \|notional\| across positions. Longs and shorts both add. | [#411] |
| **Net exposure** | Σ signed notional. Longs minus shorts. | [future] (`internal/portfolio` groups positions per instrument across accounts, without valuing them) |
| **Leverage** | Gross exposure ÷ equity. | per-position today; account-wide [#413] |
| **Buying power** | Maximum additional notional the account can open now. | `Snapshot.BuyingPower()` — placeholder in sim until [#412] |

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 1c70be6: buying power is now defined as a money amount of funds available, not a notional. How much notional it supports depends on the multiplier and margin policy.

Comment thread docs/account-risk.md Outdated
Comment on lines +143 to +144
Every rule must also always admit a de-risking proposal, so an account that is
already over a limit is never trapped (ADR-034).

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 1c70be6: the exemption is now scoped to the position/exposure-limit rules ADR-034 covers, plus the planned initial-margin rule. It notes that stateful or operational rules may legitimately reject de-risking proposals.

Comment thread docs/account-risk.md Outdated
Comment on lines +157 to +159
- For FX, per-unit risk must be in account currency (pip value). Today every
sizer and rule rejects a listing whose settlement currency differs from the
account currency rather than converting.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Confirmed: only the sizers, PerTradeLossRule, MaxInstrumentExposureRule, and MaxPositionLeverageRule check settlement currency. Fixed in 1c70be6 here and in section 6.

Comment thread docs/account-risk.md
Comment on lines +166 to +167
prospective gross notional × initial_margin_ratio ≤ equity
```

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 1c70be6: v1 applies the account ratio to each position and sums (Σ notionalᵢ × rateᵢ), so a later per-listing or per-instrument override changes only that input (#411).

Comment thread docs/account-risk.md Outdated

| # | Invariant | Status |
|---|---|---|
| 1 | At ratio 1.0, no bar ends with gross notional above the admitted limit as a result of a fill | [#414] |

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 1c70be6: the invariant is now stated per exposure-increasing fill, against the equity used to admit it, with post-entry mark drift explicitly excluded (no maintenance margin in v1).

rustyeddy and others added 2 commits September 26, 2026 10:47

@rustyeddy rustyeddy left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Reviewed PR #419 at head 53cb97c84834f60e4c6a7554bf58560dcee9dbf0.

Overall, this is a useful document and the framing is much better than the earlier draft: it correctly separates sizing, admission, broker enforcement, account state, and reporting, and it makes clear that ADRs remain authoritative.

I agree with most of Copilot's six comments, but they are mostly precision fixes rather than architectural blockers. I found one additional mismatch with the decisions we just made around #417.

1. Important: remove the implication that #417 will move Buy & Hold to full-notional sizing

The document currently says:

A capping sizer ... is an open question in #410 / #417

and later lists:

full-notional sizer headroom | TBD in #417

That no longer matches the intended Buy & Hold baseline.

We have now explicitly settled the Buy & Hold strategy contract as:

instrument
quantity
buy_date
sell_date   # optional

The strategy is quantity-driven. It should not be rewritten around FullNotionalSizer or a capping sizer simply to fit the margin milestone.

For the baseline experiment, we choose a quantity that is financeable under the account policy:

quantity * execution price <= available buying power

with suitable headroom if needed for the expected fill mechanics.

Also, the backtest end date is not an implicit sell date: if sell_date is omitted, the final position remains open and Trader values it at the final mark.

Please update the primer so #417 is described as baseline/example sizing guidance, not planned adoption of full-notional sizing by Buy & Hold.

2. Copilot's reference-price vs aggregate-mark comment is valid

The vocabulary row:

Notional ... computed by rules from Input.ReferencePrice

is too broad once account-wide margin exists.

The proposal/resulting position may use the proposal reference/fill price, while other existing positions use current snapshot marks.

The primer should distinguish those two valuation inputs explicitly.

3. Copilot's BuyingPower wording is valid

Defining BuyingPower as:

Maximum additional notional the account can open now

is too strong/general.

It is better described as the account's available financing capacity according to the account/broker model. Translating that into additional units/notional depends on price, multiplier, and margin policy.

That also fits the existing full-notional sizer behavior better.

4. Scope the de-risking exemption

This sentence:

Every rule must also always admit a de-risking proposal

overstates ADR-034.

For the margin/exposure/position-limit family we are discussing, yes: reductions must not be trapped.

But that should not become a universal rule contract for all future risk/operational gates. I agree with narrowing the wording.

5. Narrow the cross-currency statement

Likewise, this:

Today every sizer and rule rejects a listing whose settlement currency differs...

is broader than the implementation.

The correct statement is closer to:

Sizers and monetary-valued rules that require account-currency valuation currently reject cross-currency inputs rather than converting them.

Quantity/count-only rules need not inspect currency.

6. Per-instrument margin extensibility: document it, but do not expand v1 scope

I partially agree with Copilot here.

The primer should say that the shared aggregate calculation must not make future per-instrument rates impossible. A later listing/instrument-specific margin policy should be able to provide each position's requirement without changing the aggregate model.

However, I would not use this documentation PR to strengthen #411 into requiring mixed per-instrument rates in v1. We already discussed keeping this milestone intentionally narrow:

one account
one account currency
one configured initial_margin_ratio

Design for the extension; don't implement it yet.

7. The invariant wording should explicitly allow post-fill market drift

I agree with Copilot's final comment.

The important invariant is not:

gross notional * ratio <= current equity on every bar

because v1 intentionally has no maintenance-margin liquidation.

It is:

No fill may leave the account above the admission limit as a result of that fill. A later breach caused solely by market/equity drift is allowed in v1; subsequent exposure-increasing fills remain blocked while de-risking remains allowed.

That wording will prevent #414 tests from accidentally becoming maintenance-margin tests.

8. One small sequencing/documentation adjustment

The sequencing section currently lists #414 before #415, which is fine numerically, but I would preserve the dependency nuance we agreed on:

#412
  ├─> #413 -> #414
  └─> #415

#415 can proceed in parallel with #413/#414 once #410/#411/#412 are settled.

Not a blocker, just worth saying clearly.

Conclusion

I would make the documentation corrections above before merging, particularly the #417 / Buy & Hold sizing mismatch because that otherwise records a direction we have explicitly decided against.

After those edits, I think this primer will be a useful companion to ADR-066 rather than competing with it.

- Notional: distinguish the proposal's reference price from the current
  marks used for aggregate gross exposure.
- Buying power: define it as funds available (money), not notional.
- Limit the de-risking exemption to the position/exposure rules ADR-034
  covers.
- Correct the cross-currency claims: count/quantity rules do not check
  currency.
- State that v1 applies the account ratio to each position, so a later
  per-instrument rate only changes that input.
- Define the fill invariant against admission-time equity, and exclude
  post-entry mark drift.

Refs #418

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@rustyeddy rustyeddy left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Final re-review of PR #419 at head 1c70be6621f5898e49d34f99d60442bac61fa2af.

Most of the previous review items are fixed:

  • proposal reference price vs aggregate current marks is clarified;
  • BuyingPower is correctly described as a money amount rather than notional;
  • the de-risking exemption is scoped appropriately;
  • the cross-currency wording is now accurate;
  • post-entry mark drift is explicitly excluded from the v1 margin invariant;
  • sequencing now notes that #415 can proceed in parallel after #412.

There is one remaining mismatch from my prior review:

#417 is still described as full-notional/capping-sizer work

The document still says:

A capping sizer ... whether to add one is an open question in #410 / #417.

and the planned-parameters table still includes:

full-notional sizer headroom | TBD in #417

That no longer matches the Buy & Hold baseline contract we explicitly settled.

Buy & Hold should remain:

instrument
quantity
buy_date
sell_date   # optional

The strategy is quantity-driven. #417 should cover choosing/documenting a financeable baseline quantity and margin/sizing guidance, not changing Buy & Hold to use FullNotionalSizer or adding sizer headroom for it.

Please remove the #417 linkage from the capping-sizer paragraph and replace the planned-parameter row with wording consistent with the actual baseline contract, for example:

Buy & Hold baseline quantity | explicit config | choose a quantity that fits available buying power with suitable fill headroom

or simply omit that row and leave the detailed guidance to #417.

Once that is corrected, I do not see another substantive blocker in this documentation PR.

Current state: not quite merge-ready; expected merge-ready after this final documentation correction.

- Take #417 out of the capping/full-notional sizer wording. Buy & Hold
  takes an explicit quantity; #417 is guidance on choosing a quantity
  the account can finance.
- Say that per-instrument margin rates are designed for but not
  implemented in v1.
- Invariant: while over the limit because of price drift, further
  exposure increases stay blocked and de-risking stays allowed.
- Show the #410-#417 dependency graph explicitly.

Refs #418

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rustyeddy

Copy link
Copy Markdown
Owner Author

Addressed the remaining item from the re-review in 17af7c9:

Issue bodies updated to match:

@rustyeddy rustyeddy left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Final review of PR #419 at head 17af7c9a58814398c724ea4e68f6ca353f1cc001.

The last remaining issue from my prior review is now corrected.

The primer now explicitly states that Buy & Hold remains quantity-driven:

instrument
quantity
buy_date
sell_date   # optional

and correctly separates responsibilities:

  • strategy chooses quantity and timing;
  • margin/risk decides whether that quantity is admissible;
  • simulator determines the actual fill;
  • reporting values the result;
  • run end is not an implicit sell, so an open position remains open and is valued at the final mark.

It also now makes clear that a capping sizer is future work and is not part of this milestone or #417.

I re-checked the other previously raised items as well:

  • proposal reference price vs existing-position marks is clear;
  • BuyingPower wording is corrected;
  • de-risking exemption is scoped correctly;
  • cross-currency wording matches current implementation;
  • per-instrument margin extensibility is documented without expanding v1 scope;
  • the invariant allows post-entry mark/equity drift and only prohibits fills that themselves create an over-limit state;
  • sequencing notes #415 can proceed in parallel once the shared foundation is in place.

I do not see another substantive issue in the current documentation.

PR #419 is merge-ready from my review.

GitHub currently shows no combined-status entries for this head, so this assessment is based on the PR contents and review state rather than an external CI status signal.

@rustyeddy
rustyeddy merged commit 1c5fe5c into main Sep 27, 2026
1 check failed
@rustyeddy
rustyeddy deleted the docs/418-account-risk-primer branch September 27, 2026 14:47
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.

docs: account risk and capital management primer mapped to Trader

2 participants