You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit bebdc93
Browse filesBrowse the repository at this point in the historyBrowse files
fix(rails): exempt DCA buys from rail 6; dca plan checks rails 3 and 5 (#853) (#869)
* fix(rails): exempt DCA buys from the per-asset concentration rail too (#853)
The operator's decision (2026-09-28): rail 6 (per_asset_concentration_cap,
max_per_asset_pct x max_exposure_usd, $300 live) measures at cost and still
vetoed DCA buys after #841 exempted DCA from rail 4. Live BTC DCA was on
track to be refused from around end October 2026. Mirroring #841's shape:
- guards.check: rail 6 now gates on `is_buy and not intent.is_dca`. DCA
holdings still count in the exposure figure a rule-trading entry sees;
the exemption is from being gated by the rail, not a blind spot in what
it measures. Module docstring updated everywhere it claimed rail 6 binds
DCA.
- SimAccount.can_open: the same exemption, so `keel simulate` models live.
- docs/rails: new rail-6 page with the DCA exemption table; rail-4 and
rail-14 pages, the operator runbook, and the dca-plan planning doc's
#842 note updated so nothing still claims rail 6 binds DCA. Config
templates' caps comment says the same (repo config.yaml re-synced with
its template).
- Rails 2 (per order), 3 (per day), 5 (correlated) and 13 (cash) still bind
a DCA buy; rail 14 (the venue plan's attested monthly buy cap) and
available cash are now DCA's aggregate bounds on both axes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(dca-plan): warn on rail 5 and block on rail 3 for DCA's new aggregate bounds (#853)
With rails 4 and 6 now exempting DCA (#841/#853), rail 14 (the monthly
buy cap) and available cash are DCA's real aggregate bounds -- but rails
2, 3, 5 and 13 still bind a DCA buy, and `keel dca plan` said nothing
about the two of those it can evaluate offline.
- New BLOCKER (rail 3, per-day cap): the busiest single day every DCA
rule's cadence can coincide on. `Dca.detect`'s scheduling rule
(`epoch_day % cadence_days == 0`) carries no per-rule phase, so a day
exists where every rule -- this plan's own buys and every existing live
DCA rule -- buys at once; the worst day is simply the sum of every
planned per-buy plus every live rule's own per-buy. Carried on `DcaPlan`
as `worst_day_cycle_usd`/`existing_live_daily_usd`/`worst_day_spend_usd`,
mirroring the `worst_month_*` fields. The text says "DCA buys alone",
since rail 3 counts every BUY that day, not just DCA.
- New WARNING (rail 5, correlated sizing): a per-buy above
`max_per_order_usd x CORRELATED_SIZE_SCALE` for an asset not in
`UNCORRELATED_ASSETS` (both imported from guards.py, never re-typed),
naming the asset(s). It is a warning, not a blocker, because it only
binds while another correlated asset already has exposure open.
- Both flow through `plan.blockers`/`plan.warnings`, which the CLI
(`render_dca_plan`) and the /rules card payload already render
unmodified -- no payload.py change needed.
- docs/superpowers/plans/2026-09-27-dca-plan.md's #842 note amended:
rail 6 is exempt from DCA too now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(dca-plan): rail 3 and rail 5 sentences come from one service function each (#853)
per_day_cap_text and correlated_size_text build the two new sentences,
and plan.blockers / plan.warnings carry them verbatim to the CLI and the
card. The rulings are renumbered R21/R22 (R20 is taken by #854) and
recorded in the plan doc. Tests now assert the exact blocker tuple and
the exact rail-5 warnings instead of substrings, and the rail-6 sell
test asserts an empty violation set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(dca-plan): rail 5's warning no longer claims the rail rarely binds (#871)
DCA never sells, so once another correlated asset is held every buy above
the correlated-size cap is vetoed. The sentence and R22 now say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(dca): the rule's docstring lists rail 6 among DCA's exemptions (#853)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/superpowers/plans/2026-09-27-dca-plan.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -66,6 +66,7 @@
66
66
- Why: a warning that states something false about the money path is worse than none.
67
67
- Cost if wrong: none.
68
68
- Note (2026-09-28, after #842): rail 4 (`max_exposure_usd`) no longer vetoes DCA buys; rails 14 and 6 still bind them. The plan's output must not describe `max_exposure_usd` as a limit on DCA.
69
+
-**Amended 2026-09-28 (#853):** rail 6 (`per_asset_concentration_cap`, itself a fraction of `max_exposure_usd`) is now ALSO exempt from DCA, mirroring rail 4's #841 exemption -- the operator's decision was that a fixed per-asset ceiling would halt a single-asset accumulation sleeve the same way a fixed total ceiling would. Rail 14 (the venue plan's attested monthly buy cap) and available cash (rail 13) are DCA's aggregate bounds now; rails 2, 3, 5 and 13 still bind it. The plan's output must not describe `max_per_asset_pct`/`max_exposure_usd` as a limit on DCA either.
69
70
- Ruling R10: `--buffer-pct` is **required**, not defaulted.
70
71
- Why: the spec's signature names it. A silent default of 0 turns "budget" into "spend" without the operator saying so.
71
72
- Cost if wrong: one extra flag to type.
@@ -97,6 +98,13 @@
97
98
- Why: writing `candidate` rules is not a capability increase. `keel rules add` writes the same rows ungated and has no row there. A `candidate` rule cannot place an order until `rules promote` clears it.
98
99
- Cost if wrong: `tests/test_capabilities.py` scans only `_require_interactive_confirmation` call sites, and this feature adds none, so no test would object. The only cost would be an inventory row the user wanted.
99
100
101
+
- Ruling R21 (2026-09-28, #853): rail 3 (per-day cap) is a **blocker**. The plan computes the busiest single day as the sum of every planned per-buy plus every existing `live` DCA rule's own `budget_usd`. It compares that sum against `caps.max_per_day_usd`. `per_day_cap_text` builds the sentence, and both front-ends show it verbatim.
102
+
- Why: `Dca.detect` buys when `epoch_day % cadence_days == 0`, and no rule has a phase offset. So some day (a multiple of the lcm of every cadence) has every rule buying at once. If that total is over the cap, every such day would be vetoed partway through.
103
+
- Cost if wrong: if `Dca` ever gains a phase, the blocker overstates the risk. It would block conservatively, never let too much through.
104
+
- Ruling R22 (2026-09-28, #853): rail 5 (correlated-size) is a **warning**. It fires when a planned per-buy for an asset outside `UNCORRELATED_ASSETS` exceeds `max_per_order_usd x CORRELATED_SIZE_SCALE`. Both constants are imported from `guards.py`. `correlated_size_text` builds the sentence.
105
+
- Why: the rail binds only while another correlated asset already has exposure open, and a plan cannot see that.
106
+
- Cost if wrong: buys vetoed that the plan approved. DCA never sells, so once another correlated asset is held (a live DCA rule, or a second planned correlated asset after its first cycle) rail 5 vetoes every buy above the cap. Whether that case should block instead of warn is held as an open question on #871.
107
+
100
108
## Review Focus
101
109
102
110
1.**The per-buy amount rounds to $0.00** for a small budget or weight, for example `--budget 1` with a 1% weight. A person expects a named refusal ("BTC's per-buy rounds to $0.00"), not a traceback from `Dca.__init__` and not a silently dropped asset. Pinned in Task 3.
0 commit comments