Skip to content

Apply de-risking exemption to MaxPositionQuantityRule and MaxInstrumentExposureRule - #208

Merged
rustyeddy merged 1 commit into
mainfrom
fix/207-derisking-exemption-quantity-exposure-rules
Aug 27, 2026
Merged

Apply de-risking exemption to MaxPositionQuantityRule and MaxInstrumentExposureRule#208
rustyeddy merged 1 commit into
mainfrom
fix/207-derisking-exemption-quantity-exposure-rules

Conversation

@rustyeddy

Copy link
Copy Markdown
Owner

What changed

MaxPositionQuantityRule and MaxInstrumentExposureRule now apply the same de-risking exemption MaxPositionLeverageRule already had (PR #199): a proposal is exempt from the configured cap when the resulting position's magnitude does not exceed the current position's own magnitude.

Why it changed

Both rules had the identical latent defect PR #199 already fixed for MaxPositionLeverageRule: each compared only the resulting position's magnitude against its own configured cap, with no comparison to the current position. An account already over either cap could have a legitimate risk-reducing proposal rejected merely because the smaller resulting position was still over the limit — permanently trapped, since every move that would bring it back into compliance was itself rejected by the very rule it would have satisfied.

This was found during the M4 completion review (#189) while backfilling ADR-034 to document the de-risking exemption principle — the review's own draft incorrectly claimed both rules already had it. Rusty caught the inaccuracy on PR #206; ADR-034 was narrowed to describe only MaxPositionLeverageRule's actual behavior, and this issue (#207) was filed to close the gap.

How it was tested

  • Added regression tests mirroring MaxPositionLeverageRule's own three (...ReduceOnlyDeRiskingAlwaysPasses, ...NonReduceOnlyPartialDeRiskingAlwaysPasses, ...ReversalPastCurrentMagnitudeIsEvaluatedNormally) for each rule — all passed on the first run with hand-computed numbers.
  • Full existing risk test suite passes unmodified — the exemption is purely additive (an early-return before the existing cap check), so every prior passing scenario is unaffected.
  • service/execution's and pipeline's own vertical-slice tests (which use MaxPositionQuantityRule in their real risk-rejection scenario) still pass — that scenario is a genuine increase, not de-risking, so it's unaffected by this change.
  • go build ./..., go vet ./..., gofmt -l ., go test ./... -race all clean. risk package coverage: 89.2%.

Which documentation changed

Widened ADR-034 (docs/arch/adr-034-derisking-exemption-position-limit-rules.org) back to describing the exemption as applied universally across all three rules, per the plan recorded when PR #206's review narrowed it pending this fix.

Closes #207.

…ntExposureRule (#207)

Both rules had the identical latent defect PR #199 already fixed for
MaxPositionLeverageRule: each compared only the resulting position's
magnitude against its own configured cap, with no comparison to the
current position. An account already over either cap could have a
legitimate risk-reducing proposal rejected merely because the smaller
resulting position was still over the limit -- permanently trapped,
since every move that would bring it back into compliance was itself
rejected by the very rule it would have satisfied.

Fixed by applying the identical exemption MaxPositionLeverageRule
already established: a proposal is exempt from the cap when the
resulting position's magnitude does not exceed the current position's
own magnitude, computed via the same shared resultingPosition/
findPosition helpers -- never mixing the current position's own
AvgPrice with a live ReferencePrice.

Added regression tests mirroring MaxPositionLeverageRule's own three
(ReduceOnly de-risking, non-ReduceOnly partial de-risking, and a
reversal past the current magnitude still evaluated normally) for
each rule.

Widened ADR-034 back to describing the exemption as applied
universally across all three rules, per the plan recorded when PR
#206's review narrowed it to MaxPositionLeverageRule only pending this
fix.

Tested: go build ./..., go vet ./..., gofmt -l ., go test ./... -race
all clean. risk package coverage: 89.2%. Vertical-slice tests
(service/execution, pipeline) unaffected -- their own rejection
scenario is a genuine increase, not de-risking.
Copilot AI lite review requested due to automatic review settings August 27, 2026 21:54

@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 latest head dfe258c. This closes the gap identified during PR #206 cleanly. Both MaxPositionQuantityRule and MaxInstrumentExposureRule now use the existing findPosition / resultingPosition semantics and compare resulting quantity magnitude to current quantity magnitude before applying their caps, matching the already-established MaxPositionLeverageRule policy rather than inventing a second notion of de-risking. The tests cover the important boundaries: already-over-cap ReduceOnly reduction, non-ReduceOnly partial reduction, and reversal past the current magnitude still being evaluated normally. I also checked the ordering in MaxInstrumentExposureRule: the exemption occurs before requiring ReferencePrice, which is appropriate because the de-risking classification is quantity-based by ADR-034 and should not require a valuation input merely to permit exposure reduction. ADR-034 is now accurate again for all three rules. I don't see a blocker; looks ready to merge.

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.

🟢 Approval recommended

The core logic and regression tests align with the established de-risking exemption behavior, with only minor optional follow-ups noted.

Pull request overview

This PR extends the “de-risking exemption” behavior (previously implemented for MaxPositionLeverageRule) to MaxPositionQuantityRule and MaxInstrumentExposureRule, ensuring risk-reducing proposals are not rejected solely because the resulting position remains over a configured cap.

Changes:

  • Added a de-risking exemption early-return to MaxPositionQuantityRule.Evaluate.
  • Added the same de-risking exemption early-return to MaxInstrumentExposureRule.Evaluate.
  • Added regression tests for both rules mirroring the existing leverage rule de-risking coverage, and updated ADR-034 to describe the now-unified behavior.
File summaries
File Description
risk/maxpositionquantity.go Adds de-risking exemption to the quantity cap evaluation.
risk/maxpositionquantity_test.go Adds regression tests for de-risking and reversal boundary behavior for quantity cap.
risk/maxinstrumentexposure.go Adds de-risking exemption to the exposure cap evaluation.
risk/maxinstrumentexposure_test.go Adds regression tests for de-risking and reversal boundary behavior for exposure cap.
docs/arch/adr-034-derisking-exemption-position-limit-rules.org Updates ADR-034 to reflect the exemption being implemented across all three rules.
Review details
  • Files reviewed: 5/5 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.

Comment on lines +47 to 68
pos, hasPosition := findPosition(in.Account, in.Proposal.Listing)
currentQty := num.Quantity{}
if hasPosition {
currentQty = pos.Quantity
}

_, qty, err := resultingPosition(in.Account, in.Proposal)
if err != nil {
return RuleResult{}, fmt.Errorf("max position quantity: %w", err)
}

// De-risking is exempt from the cap: see this rule's own doc
// comment and ADR-034. Comparing magnitude directly (never the
// current position's own AvgPrice) matches MaxPositionLeverageRule's
// established comparison exactly.
if qty.Cmp(currentQty) <= 0 {
return RuleResult{}, nil
}

if qty.Cmp(r.max) <= 0 {
return RuleResult{}, nil
}
Comment on lines 8 to +9
this principle during implementation, but it was never captured as a
standalone ADR — only as inline doc comments on =MaxPositionLeverageRule=,
the one rule that currently applies it.
standalone ADR — only as inline doc comments on =MaxPositionLeverageRule=.
@rustyeddy
rustyeddy merged commit bd63d7b into main Aug 27, 2026
2 checks passed
@rustyeddy
rustyeddy deleted the fix/207-derisking-exemption-quantity-exposure-rules branch August 27, 2026 22:04
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.

Apply de-risking exemption to MaxPositionQuantityRule and MaxInstrumentExposureRule

2 participants