From 3d71730a1c04fd6831cde0f25f51e4f61b2c03c2 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Sun, 27 Sep 2026 09:17:06 -0400 Subject: [PATCH 1/4] fix(rails): rail 14 is a monthly buy cap, not a fee-free allowance (#836) keel trades through Coinbase Advanced Trade, which has no fee-waiver tier: on 2026-09-27 the account reported the Intro tier (0.9% taker, has_promo_fee false) and every live fill had paid a fee. Coinbase One's zero trading fees apply on the main Coinbase platform only. Wording only -- guards.py is untouched and every rail 14 number is the same: - subscription set help, the throughput report, the significance verdict's zero-fee phrase and the sim's tier section say buy cap / hypothetical zero-fee regime, not fee-free allowance; - the rail 14 doc drops the fee-waiver and profitability-boundary claim and records the correction; - the runbook and research toolkit are corrected in place; - five records that described a fee-free regime gain a dated fee note (appended, never rewritten), pinned by a test. Co-Authored-By: Claude Opus 5.5 --- .../2026-08-21-rule-family-significance.md | 8 ++ .../2026-08-22-trailing-vs-static-exits.md | 8 ++ .../2026-09-05-restatement-restated.md | 8 ++ .../2026-09-27-accumulation-policy.md | 7 ++ docs/operator-runbook.md | 3 +- docs/rails/rail-14-subscription-allowance.md | 40 ++++---- docs/research-toolkit.md | 11 ++- ...08-20-quant-lab-note-cross-verification.md | 8 ++ keel/cli.py | 3 +- keel/commands/research.py | 6 +- keel/commands/simulate.py | 5 +- keel/commands/subscription.py | 5 +- keel/research/pooled_review.py | 5 +- keel/research/significance.py | 18 ++-- keel/research/throughput.py | 14 +-- keel/sim/account.py | 6 +- keel/sim/portfolio_sim.py | 7 +- keel/sim/report.py | 7 +- keel/sim/tiers.py | 6 +- packages/keel-core/keel_core/config.py | 8 +- packages/keel-core/keel_core/subscription.py | 2 +- tests/research/test_significance.py | 8 +- tests/test_rail14_is_a_buy_cap.py | 99 +++++++++++++++++++ 23 files changed, 232 insertions(+), 60 deletions(-) create mode 100644 tests/test_rail14_is_a_buy_cap.py diff --git a/docs/experiments/2026-08-21-rule-family-significance.md b/docs/experiments/2026-08-21-rule-family-significance.md index a1601d48..aefd4e7b 100644 --- a/docs/experiments/2026-08-21-rule-family-significance.md +++ b/docs/experiments/2026-08-21-rule-family-significance.md @@ -1,5 +1,13 @@ # Rule-family significance: is a family's edge distinguishable from zero at the fee actually paid? +> **Fee note (added 2026-09-27, #836).** This record describes a fee-free regime inside a +> venue's monthly allowance. For keel's orders that regime is **hypothetical**: they go through +> Coinbase Advanced Trade, which has no fee-waiver tier. On 2026-09-27 the account reported the +> Intro tier (0.9% taker, 0.5% maker, `has_promo_fee: false`), and every live fill had paid a +> fee; Coinbase One's zero trading fees apply on the main Coinbase platform, not Advanced +> Trade. Figures priced at the taker fee are the realistic ones; figures priced at zero fee +> describe a regime keel cannot reach. Nothing on this page has been rewritten. + **Issue #475.** Driver: [`2026-08-21-rule-family-significance.py`](2026-08-21-rule-family-significance.py) (pre-registered in its docstring before the run). Artifact: every number below is a row of [`2026-08-21-rule-family-significance.jsonl`](2026-08-21-rule-family-significance.jsonl) — 180 diff --git a/docs/experiments/2026-08-22-trailing-vs-static-exits.md b/docs/experiments/2026-08-22-trailing-vs-static-exits.md index 3c0cf7ce..4614e517 100644 --- a/docs/experiments/2026-08-22-trailing-vs-static-exits.md +++ b/docs/experiments/2026-08-22-trailing-vs-static-exits.md @@ -1,5 +1,13 @@ # Trailing vs static exits: does the ratchet-only exit policy help, at the fee actually paid? +> **Fee note (added 2026-09-27, #836).** This record describes a fee-free regime inside a +> venue's monthly allowance. For keel's orders that regime is **hypothetical**: they go through +> Coinbase Advanced Trade, which has no fee-waiver tier. On 2026-09-27 the account reported the +> Intro tier (0.9% taker, 0.5% maker, `has_promo_fee: false`), and every live fill had paid a +> fee; Coinbase One's zero trading fees apply on the main Coinbase platform, not Advanced +> Trade. Figures priced at the taker fee are the realistic ones; figures priced at zero fee +> describe a regime keel cannot reach. Nothing on this page has been rewritten. + > **Cost note (added 2026-09-02).** The figures below are priced at the flat 5bp > slippage floor. [the per-product restatement](2026-09-01-per-product-slippage-restatement.md) later measured that **no > asset in keel's universe reaches that floor** — the range is 1.1× to 36.8× — so every diff --git a/docs/experiments/2026-09-05-restatement-restated.md b/docs/experiments/2026-09-05-restatement-restated.md index 91f0ff5c..a2945496 100644 --- a/docs/experiments/2026-09-05-restatement-restated.md +++ b/docs/experiments/2026-09-05-restatement-restated.md @@ -1,5 +1,13 @@ # The 08-13 restatement, restated: the engine never moved the numbers — three weeks of candles did +> **Fee note (added 2026-09-27, #836).** This record describes a fee-free regime inside a +> venue's monthly allowance. For keel's orders that regime is **hypothetical**: they go through +> Coinbase Advanced Trade, which has no fee-waiver tier. On 2026-09-27 the account reported the +> Intro tier (0.9% taker, 0.5% maker, `has_promo_fee: false`), and every live fill had paid a +> fee; Coinbase One's zero trading fees apply on the main Coinbase platform, not Advanced +> Trade. Figures priced at the taker fee are the realistic ones; figures priced at zero fee +> describe a regime keel cannot reach. Nothing on this page has been rewritten. + **Date:** 2026-09-05 · **Restates:** [`2026-08-13-restated-under-a-production-faithful-engine.md`](2026-08-13-restated-under-a-production-faithful-engine.md) · **Engine:** `keel` at `main`, every code path this run exercises **byte-identical to `v0.13.3`** diff --git a/docs/experiments/2026-09-27-accumulation-policy.md b/docs/experiments/2026-09-27-accumulation-policy.md index 44518d69..53cb7c2a 100644 --- a/docs/experiments/2026-09-27-accumulation-policy.md +++ b/docs/experiments/2026-09-27-accumulation-policy.md @@ -1,5 +1,12 @@ # Accumulation policy after fees: no arm beats static DCA +> **Fee note (added 2026-09-27, #836).** The headline "allowance-aware" fee model priced buys +> fee-free up to $500 a month. For keel's orders that regime is **hypothetical**: they go +> through Coinbase Advanced Trade, which charges its maker/taker schedule on every order (0.9% +> taker on 2026-09-27), so the **flat-taker sensitivity run is the one that matches live**. The +> verdict is identical under both fee models, so it is unaffected. Nothing on this page has +> been rewritten. + **Date:** 2026-09-27 **Issue:** #831 · **Design:** [`2026-09-27-accumulation-policy-design.md`](../superpowers/specs/2026-09-27-accumulation-policy-design.md) (revision 2, approved as #834) diff --git a/docs/operator-runbook.md b/docs/operator-runbook.md index de232266..90023584 100644 --- a/docs/operator-runbook.md +++ b/docs/operator-runbook.md @@ -998,7 +998,8 @@ fourth does not, which is most of why they drift apart. actual equity it stops binding before available cash does, and the refusal comes later and less legibly from the funding check (rail 13). In paper mode the proxy is bypassed: sizing uses the paper account's own equity. -- **rail 14's monthly allowance** — the fee-free monthly BUY volume. It lives in the **database, +- **rail 14's monthly buy cap** — the most the agent may BUY in a UTC month. It is a cap, not a + fee waiver: Advanced Trade orders pay the venue's maker/taker fee inside it (#836). It lives in the **database, not `config.yaml`**: the `broker_subscriptions` row written by `keel subscription attest --venue coinbase --tier `, or set directly with `keel subscription set --free-volume-usd N`. `config.yaml` only supplies the tier catalogue and the unattested fallback diff --git a/docs/rails/rail-14-subscription-allowance.md b/docs/rails/rail-14-subscription-allowance.md index a324c2b8..5463f7ad 100644 --- a/docs/rails/rail-14-subscription-allowance.md +++ b/docs/rails/rail-14-subscription-allowance.md @@ -1,25 +1,27 @@ -# Rail 14 — subscription allowance (the fee-free monthly volume cap) +# Rail 14 — the monthly buy cap Rail 14 is one of keel's hard rails: checks in `keel/execution/guards.py` that run before every order, in every mode, and cannot be switched off or widened from `config.yaml`. This one caps the **month-to-date live BUY notional** — everything the agent has bought this UTC calendar -month, from the orders audit log, plus the order being placed — at the venue's **fee-free -monthly volume allowance**. - -## What it bounds — and why that is a profitability boundary, not a budget - -The cap is the subscription tier's `free_volume_usd`: the monthly volume the venue (Coinbase -Advanced Trade, at time of writing) lets the account trade **taker-fee-free**. That makes the -rail's economics, not just its accounting: - -> Inside the allowance the measured strategies are indistinguishable from break-even; outside -> it the taker fee (120 bp) makes them decisively negative. **"It is not a budget limit; it is -> the profitability boundary."** -> — `docs/research/2026-08-20-quant-lab-note-cross-verification.md`, §5 - -On the hourly clock every round trip outside the allowance costs more than one full unit of -risk, so the rail is what keeps the agent from buying a known-losing trade. DCA is **not** -exempt — recurring buys are exactly the spend this rail exists to cap. +month, from the orders audit log, plus the order being placed — at the venue's attested +**monthly buy cap** (`free_volume_usd` in the record; the field keeps its historical name). + +## What it bounds: spend, not fees + +The cap is a limit keel puts on its **own** buying. It is not a fee waiver, and nothing inside +it is free: keel places its orders through **Coinbase Advanced Trade**, which charges its +maker/taker schedule on every order whatever the cap says (#836). DCA is **not** exempt — +recurring buys are exactly the spend this rail exists to cap. + +> **Correction (2026-09-27, #836).** This page used to describe the cap as volume the venue +> waives the taker fee on, and as *"the profitability boundary"*. That was wrong for keel's +> orders. On 2026-09-27 the account's own `get_transaction_summary` reported +> the **Intro** tier (0.9% taker, 0.5% maker) with `has_promo_fee: false`, and every live fill +> had paid a fee. Coinbase One's "zero trading fees up to $500/month" applies on the main +> Coinbase platform, not Advanced Trade, where the listed Coinbase One benefit is a fee +> rebate. The rail's mechanism and every number it enforces are unchanged; only the claim +> about fees was removed. Research that modelled an "inside the allowance, fee-free" regime +> carries a dated note saying that regime is hypothetical for keel's orders. ## Where the setting lives — the database, not config.yaml @@ -32,7 +34,7 @@ on every order, so a new attestation takes effect on the very next one, with no # Assert which tier the venue is on (the normal path; upgrades exactly one number) keel subscription attest --venue --tier -# Escape hatch: a raw allowance without naming a tier (recorded as tier=unknown) +# Escape hatch: a raw monthly buy cap without naming a tier (recorded as tier=unknown) keel subscription set --venue --free-volume-usd 500 ``` diff --git a/docs/research-toolkit.md b/docs/research-toolkit.md index 9248e777..dee9f6da 100644 --- a/docs/research-toolkit.md +++ b/docs/research-toolkit.md @@ -34,7 +34,7 @@ carefully as the "answers" column — that column is the actual product. | `deflate.py` | given N trials tried, the Sharpe bar the winner had to clear, and how much data that needs | what N and correlation to assume — it reports a band across assumptions rather than guess one | `keel research deflate` (`keel trials deflate`) | | `walkforward.py` | does a GIVEN fixed parameter set hold up across rolling train/test windows, and does it degrade | which parameter set, fold or window is best — none is ever computed | `keel research walk-forward` (`keel trials walk-forward`) | | `independence.py` | how much two rules' (or two horizons') signals overlap in time, position and P&L | whether either rule is profitable, or which one to keep | `keel research independence` | -| `throughput.py` | how much volume a fee-free allowance can honestly carry this month, and how long evidence takes to accumulate | it never enlarges an allowance to fit a plan — a product that doesn't fit is deferred, not squeezed in | `keel research throughput` | +| `throughput.py` | how much volume a venue's monthly buy cap can honestly carry this month, and how long evidence takes to accumulate | it never enlarges an allowance to fit a plan — a product that doesn't fit is deferred, not squeezed in | `keel research throughput` | | `cts_factors.py` | do the 11 CTS confluence factors carry independent evidence, or is one momentum read counted three times | the biased ("obvious") conditional sample is computed but never allowed to carry the headline | `keel research factors` | | `tuning.py` | for a declared parameter space, does a train/held-out study produce a candidate clearing held-out sign AND PBO ≤ 0.5 | it never auto-tunes a live/paper profile, and a pass is a hypothesis, not a promotion | `keel research tuning` | | `bias.py` | does a rule's decision at bar N change when bars after N become visible (lookahead / recursive drift) | whether the rule is profitable — this is about information leakage only | `keel research lookahead` (`keel rules lookahead`) | @@ -55,10 +55,11 @@ command surface just doesn't duplicate what already works. The question is a one-proportion test against break-even, with the null set by the fee *actually paid*: `keel/research/significance.py` prices the same reconstructed trades at both fee regimes -a keel deployment can be in — the 120 bp taker fee outside the venue's fee-free allowance, and -zero inside it — and never averages the two, because the cross-verification behind #475 found the -fee difference *is* the result (decisively negative outside, indistinguishable from break-even -inside). It also refuses to pool trades as if they were independent: signals fire in herds (about +it was designed around — the 120 bp taker fee, and zero — and never averages the two, because the +cross-verification behind #475 found the fee difference *is* the result (decisively negative at +the taker fee, indistinguishable from break-even at zero). **The zero-fee regime is hypothetical +for keel's orders** (#836): they go through Coinbase Advanced Trade, which has no fee-waiver +tier, so the taker-fee figures are the realistic ones. It also refuses to pool trades as if they were independent: signals fire in herds (about eight assets the same UTC day, ICC 0.212), so `n_eff` divides the pooled count by `throughput.design_effect()` before any standard error is formed — a pooled 100 comes out to roughly 39 effective observations, not 100. diff --git a/docs/research/2026-08-20-quant-lab-note-cross-verification.md b/docs/research/2026-08-20-quant-lab-note-cross-verification.md index 1ee800fb..e8ecbd1d 100644 --- a/docs/research/2026-08-20-quant-lab-note-cross-verification.md +++ b/docs/research/2026-08-20-quant-lab-note-cross-verification.md @@ -1,5 +1,13 @@ # Cross-verification of the Keel Quant Lab modelling note +> **Fee note (added 2026-09-27, #836).** This record describes a fee-free regime inside a +> venue's monthly allowance. For keel's orders that regime is **hypothetical**: they go through +> Coinbase Advanced Trade, which has no fee-waiver tier. On 2026-09-27 the account reported the +> Intro tier (0.9% taker, 0.5% maker, `has_promo_fee: false`), and every live fill had paid a +> fee; Coinbase One's zero trading fees apply on the main Coinbase platform, not Advanced +> Trade. Figures priced at the taker fee are the realistic ones; figures priced at zero fee +> describe a regime keel cannot reach. Nothing on this page has been rewritten. + **Contribution:** *Keel Quant Lab — Couche de modélisation stochastique pour Keel* ([PDF](Keel_Quant_Lab_note_pedagogique_260820_103629.pdf)), by **Dr. Issam Elhattab**, August 2026. diff --git a/keel/cli.py b/keel/cli.py index 5add21d3..76ace67b 100644 --- a/keel/cli.py +++ b/keel/cli.py @@ -1367,7 +1367,8 @@ def simulate( `docs/superpowers/plans/2026-07-17-engine-validation-simulation.md` Task 8. Also computes a Coinbase One subscription-tier/fee analysis (Issue #86): for each configured - tier (`config.tiers`), whether staying WITHIN its fee-free monthly trading-volume allowance + tier (`config.tiers`), whether staying WITHIN its monthly trading-volume allowance -- priced at + zero fee, which is hypothetical for keel's Advanced Trade orders (#836) -- (a separate, throttled sim run per finite-free-volume tier) or trading freely and paying the taker fee on volume EXCEEDING it ("over cap") nets out ahead. This means up to 3 total sim passes (natural + one throttled run per finite-free-volume tier) unless `--skip-within-cap`. diff --git a/keel/commands/research.py b/keel/commands/research.py index 4d8c8de0..c4494d13 100644 --- a/keel/commands/research.py +++ b/keel/commands/research.py @@ -199,8 +199,8 @@ class ResearchModuleEntry: ResearchModuleEntry( module="throughput.py", question=( - "Allowance-throughput planning: how many signals a venue's fee-free volume " - "allowance can actually carry per month, and how long -- in EFFECTIVE " + "Allowance-throughput planning: how many signals a venue's monthly buy cap " + "(rail 14) can actually carry per month, and how long -- in EFFECTIVE " "observations, via the herding design effect -- the evidence honestly takes to " "gather." ), @@ -755,7 +755,7 @@ def research_throughput( products_json: str | None, allowances_json: str | None, ) -> None: - """Allowance-throughput planning: how many signals a fee-free allowance can carry a month, + """Allowance-throughput planning: how many signals a monthly buy cap can carry a month, and how long the evidence honestly takes to accumulate (throughput.py). Pure arithmetic -- no db, no candles, no rule. `--venues-json` states what diff --git a/keel/commands/simulate.py b/keel/commands/simulate.py index e227d946..3c2a623c 100644 --- a/keel/commands/simulate.py +++ b/keel/commands/simulate.py @@ -384,8 +384,9 @@ def run_simulation( where the old command body built it -- after option parsing, before the coverage read. Also computes a Coinbase One subscription-tier/fee analysis (Issue #86): for each - configured tier (`config.tiers`), whether staying WITHIN its fee-free monthly - trading-volume allowance (a separate, throttled sim run per finite-free-volume tier) or + configured tier (`config.tiers`), whether staying WITHIN its monthly trading-volume + allowance -- priced at zero fee, hypothetical for keel's Advanced Trade orders (#836) -- + (a separate, throttled sim run per finite-free-volume tier) or trading freely and paying the taker fee on volume EXCEEDING it ("over cap") nets out ahead. This means up to 3 total sim passes (natural + one throttled run per finite-free-volume tier) unless `skip_within_cap`. diff --git a/keel/commands/subscription.py b/keel/commands/subscription.py index 90c63a08..8a02db5c 100644 --- a/keel/commands/subscription.py +++ b/keel/commands/subscription.py @@ -228,7 +228,10 @@ def subscription_attest( "--free-volume-usd", "free_volume_raw", required=True, - help="Raw fee-free monthly volume in USD, e.g. 500.", + help=( + "Monthly BUY cap in USD (rail 14), e.g. 500. A cap keel puts on its own buying, " + "not a fee waiver: Advanced Trade orders pay the venue's maker/taker fee (#836)." + ), ) @click.option( "--pacing", diff --git a/keel/research/pooled_review.py b/keel/research/pooled_review.py index 18ae4ac3..ff386d5e 100644 --- a/keel/research/pooled_review.py +++ b/keel/research/pooled_review.py @@ -364,8 +364,9 @@ def significance(self) -> FamilySignificance: def realized_fee_pct(trips: Sequence[RoundTrip]) -> Decimal: """The pool's realized fee fraction: fees paid over notional traded, both legs. - The forward trades' regime is an OUTCOME, not an assumption — inside the fee-free - allowance this is ~0, at the taker rate ~120 bp — so it is measured off the same round + The forward trades' regime is an OUTCOME, not an assumption — at a zero fee this would be + ~0 (a regime keel's Advanced Trade orders never reach, #836), at the taker rate ~120 bp — so + it is measured off the same round trips the win rate is, and reported beside it. Where a pure ledger row contributes (no orders twin), its fee is the exit leg only, so the fraction is a lower bound — `render_report` labels it rather than letting it pose as the both-legs figure. diff --git a/keel/research/significance.py b/keel/research/significance.py index d221baa9..1fb044b8 100644 --- a/keel/research/significance.py +++ b/keel/research/significance.py @@ -3,8 +3,9 @@ This module measures. It does not score, gate, or change anything. It is report-only evidence for the promotion gate, and its most important output is the refusal: the honest result to -date is that no shipped family is net positive at the 120 bp taker fee and the fee-free -allowance reconstruction sits at break-even, so a significance tool here must be able to say +date is that no shipped family is net positive at the 120 bp taker fee and the zero-fee +reconstruction sits at break-even (a regime keel's orders never reach, #836), so a significance +tool here must be able to say "not distinguishable from zero" and mean it. A tool that cannot say no is a flattery tool. The question is a one-proportion test with the break-even as the null, priced at the fee @@ -13,11 +14,14 @@ * **Break-even from payoff.** A family whose average win is `b` times its average loss breaks even at win rate `1/(1+b)` (the note's eq. 3 with `kappa` folded into the prices the backtest already charges). The edge is `win_rate - break_even`, in win-rate points. -* **Two fee regimes, never an average of them.** Outside the venue's fee-free volume - allowance every round trip pays the taker rate on both legs; inside it pays none. The same +* **Two fee regimes, never an average of them.** At the taker rate every round trip pays it + on both legs; at zero it pays none. **The zero-fee regime is hypothetical for keel's orders** + (#836): they go through Coinbase Advanced Trade, which has no fee-waiver tier, so a + deployment's realistic figure is the taker one. The same reconstructed trades are evaluated at BOTH rates because the cross-verification (`docs/research/2026-08-20-quant-lab-note-cross-verification.md` §5) showed the fee IS the - result: decisively negative outside, indistinguishable from break-even inside. `fee_pct` + result: decisively negative at the taker rate, indistinguishable from break-even at zero. + `fee_pct` is a parameter, not a constant, so a deployment threads its own `config.fees.taker_pct`. * **n_eff, never raw n (#427).** Signals fire in herds (~8 assets the same day, ICC 0.212), so pooled trades are divided by `throughput.design_effect()` before any standard error is @@ -232,7 +236,9 @@ def render_family(stat: FamilySignificance) -> list[str]: if stat.fee_pct > 0: fee_phrase = f"the {fee_bp} bp taker fee" else: - fee_phrase = "the fee-free allowance" + # A zero fee is a HYPOTHETICAL regime for keel's orders: Advanced Trade has no fee-waiver + # tier, and the account pays its maker/taker schedule inside rail 14's cap too (#836). + fee_phrase = "a zero fee (hypothetical: Advanced Trade orders are never fee-free, #836)" if stat.verdict == "insufficient_n": verdict_line = ( diff --git a/keel/research/throughput.py b/keel/research/throughput.py index 76258e4e..e9d23855 100644 --- a/keel/research/throughput.py +++ b/keel/research/throughput.py @@ -1,10 +1,10 @@ -"""Allowance-throughput planning (#478): route WITHIN fee-free allowances, and say +"""Allowance-throughput planning (#478): route WITHIN each venue's monthly buy cap, and say honestly how long the evidence takes. Two questions this module answers, both with the same discipline the engine applies everywhere else -- report the truth, refuse to flatter: -* **Throughput.** A venue's fee-free volume allowance (rail 14) caps monthly BUY +* **Throughput.** A venue's attested monthly buy cap (rail 14) caps monthly BUY notional, and the cross-verification showed that cap is the *profitability boundary*: inside it the reconstructed rules sit at break-even, outside it the taker fee decides. So the honest throughput of a venue is @@ -84,7 +84,7 @@ def detectable_edge(effective_n: Decimal) -> Decimal: @dataclass(frozen=True) class VenueThroughput: - """One venue's honest monthly throughput at its fee-free allowance. + """One venue's honest monthly throughput within its monthly buy cap. ``monthly_allowance`` is None for an unlimited (Premium, in force) record -- the same convention rail 14 uses, where an unlimited allowance has no cap to @@ -230,9 +230,11 @@ def render_report(venues: list[VenueThroughput], target_edge: Decimal) -> list[s as license to trade through the cap would be worse than no planner.""" lines: list[str] = [ "Allowance throughput plan (#478)", - "Rail 14 is the profitability boundary: these numbers route trades", - "WITHIN fee-free allowances; an allowance is never enlarged or breached", - f"to fit more. Design effect {design_effect()} (k={MEAN_EPISODE_SIZE},", + "Rail 14 is a monthly buy cap: these numbers route trades WITHIN each", + "venue's attested buy cap; a cap is never enlarged or breached to fit", + "more. Trades inside it still pay the venue's fee (Advanced Trade has no", + "fee-waiver tier, #836).", + f"Design effect {design_effect()} (k={MEAN_EPISODE_SIZE},", f"ICC={EPISODE_ICC}): pooled trades are divided by it before any", "time-to-detection claim, per #427.", "", diff --git a/keel/sim/account.py b/keel/sim/account.py index 03452437..35345127 100644 --- a/keel/sim/account.py +++ b/keel/sim/account.py @@ -84,8 +84,10 @@ `exit_fill = price * (1 - slippage_pct)`, `fee = fill * qty * fee_pct` on each leg. **Sim/live divergence, intentional and documented (Issue #85):** `_day_volume`/`_month_volume` -sum BOTH opens (buys) AND closes (sells) as trading VOLUME -- matching Coinbase One's actual -fee-free perk, which is a monthly *volume* allowance, not a buy-only spend cap. +sum BOTH opens (buys) AND closes (sells) as trading VOLUME -- matching Coinbase One's zero-fee +perk, which is a monthly *volume* allowance, not a buy-only spend cap. That perk applies on the +main Coinbase platform, not Advanced Trade where keel trades (#836), so the sim's allowance is +modelled, not a fee waiver keel's orders receive. `execution.guards.check` (rails 3/14, `_daily_spend_usd`/`_monthly_buy_spend_usd`) still counts BUY notional only, and is deliberately left untouched by this issue (live executor/guards are a separate, safety-reviewed follow-up). This means `SimAccount.can_open` and `guards.check` can diff --git a/keel/sim/portfolio_sim.py b/keel/sim/portfolio_sim.py index 4b94a581..81a003ce 100644 --- a/keel/sim/portfolio_sim.py +++ b/keel/sim/portfolio_sim.py @@ -341,8 +341,9 @@ def run( `monthly_volume_cap` (Issue #86, Coinbase One tier/fee analysis): when `None` (default), the account trades naturally -- sizing is clamped only by `SimAccount.max_affordable_notional`'s existing six caps (cash / concentration / exposure / etc, Issue #85), which can push a - month's trading VOLUME (buys+sells) past any particular subscription tier's fee-free - allowance. When set to a `Decimal`, every order's clamp ALSO floors headroom to the volume + month's trading VOLUME (buys+sells) past any particular subscription tier's monthly + volume allowance. When set to a `Decimal`, every order's clamp ALSO floors headroom to the + volume remaining before that ceiling this UTC month, so the account never trades enough in a month to exceed `monthly_volume_cap` -- i.e. it never owes a fee under a tier whose free allowance equals `monthly_volume_cap`. This throttles both the RULE-slot clamp and the DCA sleeve (DCA @@ -708,7 +709,7 @@ def _process_dca_signals( if monthly_volume_cap is not None: remaining = monthly_volume_cap - account.month_volume(now_ts) if notional > remaining: - continue # would exceed the fee-free monthly volume cap -- skip this cycle + continue # would exceed the tier's monthly volume allowance -- skip this cycle intent = OpenIntent( asset=asset, diff --git a/keel/sim/report.py b/keel/sim/report.py index 00599802..828ee875 100644 --- a/keel/sim/report.py +++ b/keel/sim/report.py @@ -991,11 +991,16 @@ def _render_tier_section(tier_results: list[TierFeeResult]) -> list[str]: lines = [ "## Subscription tier & fee analysis", "", - "For each Coinbase One tier: staying WITHIN the fee-free monthly trading-volume " + "For each Coinbase One tier: staying WITHIN the tier's monthly trading-volume " "allowance (a throttled run, 0 trading fees, subscription still due) vs trading freely " 'and paying the taker fee on volume EXCEEDING it ("over cap"). Premium\'s allowance is ' "unlimited, so its within-cap and over-cap rows are identical.", "", + "**The within-cap rows are HYPOTHETICAL for keel's orders.** Coinbase One's zero trading " + "fees apply on the main Coinbase platform, not Advanced Trade, where keel places its " + "orders and pays the maker/taker schedule inside any cap (#836). Read the over-cap rows " + "as the realistic ones.", + "", ] if not tier_results: lines.append("_No tier/fee analysis was computed for this run._") diff --git a/keel/sim/tiers.py b/keel/sim/tiers.py index 50c54895..b3811228 100644 --- a/keel/sim/tiers.py +++ b/keel/sim/tiers.py @@ -2,8 +2,10 @@ Given a completed sim run's per-month trading VOLUME (`portfolio_sim.SimResult.monthly_volume`, Issue #85's buys+sells convention) and a `keel.config.TierConfig`, this module answers: for a -given Coinbase One tier, does staying WITHIN the tier's fee-free monthly volume allowance (a -throttled run, 0 trading fees, but the subscription is still due) or trading freely and paying +given Coinbase One tier, does staying WITHIN the tier's monthly volume allowance (a throttled +run, 0 trading fees, but the subscription is still due -- a HYPOTHETICAL regime for keel's +orders, which go through Advanced Trade and pay its maker/taker fees inside any cap, #836) or +trading freely and paying the taker fee on volume EXCEEDING it ("over cap") net out ahead, once the tier's own monthly subscription cost is subtracted too? diff --git a/packages/keel-core/keel_core/config.py b/packages/keel-core/keel_core/config.py index 41abdd7c..42e67f22 100644 --- a/packages/keel-core/keel_core/config.py +++ b/packages/keel-core/keel_core/config.py @@ -67,8 +67,9 @@ def _non_negative_int(value: Any, key: str) -> int: # Issue #85: `max_per_order_usd`/`max_per_day_usd` were Phase-1 PLACEHOLDER guesses ($100/$300), -# never real Coinbase limits -- Coinbase One's only subscription constraint is monthly fee-free -# trading VOLUME (`SubscriptionConfig.assumed_free_volume_usd`), not a per-order or per-day $ cap. +# never real Coinbase limits -- Coinbase One's only subscription constraint is a monthly zero-fee +# trading VOLUME (main Coinbase platform only -- not Advanced Trade, where keel trades, #836) +# (`SubscriptionConfig.assumed_free_volume_usd`), not a per-order or per-day $ cap. # Risk-sized rule orders routinely land in the $400-24k range, so a $100/$300 default silently # rejected 100% of them. These two fields are now OPTIONAL internal risk knobs -- a user MAY # still tighten them in `config.yaml` -- but absent an explicit value they default to this @@ -180,7 +181,8 @@ class PaperConfig: class SubscriptionConfig: """Subscription-related settings. Three fields, three distinct roles — do not conflate them. - `assumed_free_volume_usd` is the **simulator's** assumed fee-free monthly volume + `assumed_free_volume_usd` is the **simulator's** assumed monthly volume allowance (priced at + zero fee in the tier analysis -- hypothetical for keel's Advanced Trade orders, #836) (`sim/account.py`'s `_monthly_allowance_cap`). It is a pinned, reproducible assumption for backtests. It is NOT the live cap: rail 14 derives that from the attested `broker_subscriptions` record, so that upgrading a tier changes one place. This field was diff --git a/packages/keel-core/keel_core/subscription.py b/packages/keel-core/keel_core/subscription.py index 013f000b..f7595578 100644 --- a/packages/keel-core/keel_core/subscription.py +++ b/packages/keel-core/keel_core/subscription.py @@ -33,7 +33,7 @@ class SubscriptionStatus(str, Enum): class BrokerSubscription: """One venue's subscription, as last attested by the user. - `free_volume_usd is None` means an UNLIMITED fee-free allowance (Premium) -- there is no cap, + `free_volume_usd is None` means an UNLIMITED monthly buy cap (Premium) -- there is no cap, which is not the same as a cap of zero. `tier_name` may be `'unknown'`, which `keel subscription set` produces: a raw cap the user diff --git a/tests/research/test_significance.py b/tests/research/test_significance.py index c49cbcc0..b85ee203 100644 --- a/tests/research/test_significance.py +++ b/tests/research/test_significance.py @@ -261,11 +261,15 @@ def test_render_family_states_regime_n_n_eff_and_verdict() -> None: assert "0.399" in text -def test_render_family_inside_allowance_uses_the_fee_free_wording() -> None: +def test_render_family_labels_a_zero_fee_regime_hypothetical() -> None: + """#836: a zero fee is not a regime keel's orders can reach -- Advanced Trade has no + fee-waiver tier -- so the verdict names it hypothetical rather than "the fee-free + allowance". The caller's regime label is still echoed verbatim.""" stat = significance("rsi_meanrev", "inside_allowance_fee_free", ZERO, _outcomes(50, 50)) text = "\n".join(render_family(stat)).lower() assert "inside_allowance_fee_free" in text - assert "fee-free" in text + assert "the fee-free allowance" not in text + assert "zero fee (hypothetical" in text assert "not distinguishable" in text diff --git a/tests/test_rail14_is_a_buy_cap.py b/tests/test_rail14_is_a_buy_cap.py new file mode 100644 index 00000000..4123d9d8 --- /dev/null +++ b/tests/test_rail14_is_a_buy_cap.py @@ -0,0 +1,99 @@ +"""Rail 14 is a monthly BUY cap, and nothing keel prints may say it buys fee-free (#836). + +keel places its orders through Coinbase Advanced Trade. On 2026-09-27 that account's own +`get_transaction_summary` reported the Intro tier (0.9% taker, 0.5% maker) with +`has_promo_fee: false`, and every live fill had paid a fee. Coinbase One's "zero trading fees +up to $500/month" applies on the main Coinbase platform, not Advanced Trade. So the attested +`free_volume_usd` is a cap keel imposes on its own buying, not a fee waiver the venue grants, +and text calling it a "fee-free allowance" told the operator their orders were free when they +were not. + +These tests pin the operator-facing wording and the append-only records. They deliberately do +NOT change what rail 14 enforces: the guard tests in `tests/execution/test_guards.py` pass +untouched, and that is the proof the numbers did not move. +""" + +from __future__ import annotations + +import re +from decimal import Decimal +from pathlib import Path + +from click.testing import CliRunner + +from keel.cli import cli +from keel.research.significance import render_family, significance +from keel.research.throughput import VenueThroughput, render_report +from keel.sim.report import _render_tier_section +from tests.research.test_significance import _outcomes + +_ROOT = Path(__file__).resolve().parent.parent +_RAIL_DOC = _ROOT / "docs/rails/rail-14-subscription-allowance.md" +_FEE_NOTE = "#836" +#: A sentence asserting the venue waives fees inside the cap. Mentioning the words to say they +#: do NOT apply is allowed; claiming them is not. +_CLAIM = re.compile( + r"lets the account trade taker-fee-free|trade taker-fee-free|fee-free monthly volume allowance", + re.I, +) + + +def test_subscription_set_help_calls_it_a_buy_cap_not_a_fee_waiver() -> None: + result = CliRunner().invoke(cli, ["subscription", "set", "--help"]) + assert result.exit_code == 0, result.output + help_text = " ".join(result.output.split()) + assert "fee-free" not in help_text.lower() + assert "buy cap" in help_text.lower() + + +def test_the_throughput_report_does_not_route_within_a_fee_free_allowance() -> None: + venue = VenueThroughput( + venue="coinbase", + monthly_allowance=Decimal("500"), + mean_trade_notional=Decimal("50"), + expected_signals_per_month=Decimal("4"), + ) + text = "\n".join(render_report([venue], target_edge=Decimal("0.124"))).lower() + assert "fee-free" not in text + assert "buy cap" in text + assert "never enlarged" in text # the rail 14 guardrail is still stated + + +def test_a_zero_fee_significance_row_is_labelled_hypothetical() -> None: + stat = significance("rsi_meanrev", "inside_allowance_fee_free", Decimal("0"), _outcomes(50, 50)) + text = "\n".join(render_family(stat)).lower() + assert "the fee-free allowance" not in text + assert "zero fee" in text + assert "hypothetical" in text + assert _FEE_NOTE in text + + +def test_the_tier_section_says_its_within_cap_rows_are_hypothetical_on_advanced_trade() -> None: + text = " ".join(_render_tier_section([])) + assert "hypothetical" in text.lower() + assert "Advanced Trade" in text + assert _FEE_NOTE in text + + +def test_the_rail_14_doc_no_longer_claims_fees_are_waived() -> None: + doc = _RAIL_DOC.read_text(encoding="utf-8") + assert not _CLAIM.search(doc), "the rail 14 doc still claims the venue waives fees" + assert "buy cap" in doc.lower() + assert _FEE_NOTE in doc + + +def test_every_record_that_described_a_fee_free_regime_carries_the_fee_note() -> None: + """Records are appended to, never rewritten. A record that priced or described a + fee-free regime keeps its text and gains a dated note pointing at #836.""" + records = sorted((_ROOT / "docs/experiments").glob("*.md")) + sorted( + (_ROOT / "docs/research").glob("*.md") + ) + missing = [ + path.name + for path in records + if "fee-free" in (text := path.read_text(encoding="utf-8")).lower() + and _FEE_NOTE not in text + ] + assert not missing, "records describing a fee-free regime without the #836 note:\n " + ( + "\n ".join(missing) + ) From ede5bda73d542712e130f35bcb58e483711baf89 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Sun, 27 Sep 2026 09:33:51 -0400 Subject: [PATCH 2/4] fix(rails): address #837 review -- the claim test can fail, and the claim is gone from templates and configs - The rail 14 doc test could not fail: its regex never matched the real pre-#836 paragraph (a line break split the phrase and ** wrapped it). It now normalises markdown and whitespace first, and a new test pins the detector against the retracted paragraph verbatim. - The fee-free claim is corrected in keel/templates/config.live.yaml and config.yaml (what keel setup writes into a deployment), the tracked config.yaml / config.paperforward.yaml / config.paper-hourly.yaml, the TierConfig docstring, and throughput.py / significance.py (#838). Comments only: every config's parsed values are identical. - The record-note rule now covers docs/superpowers/specs; four specs gain a dated fee note. Co-Authored-By: Claude Opus 5.5 --- config.paper-hourly.yaml | 9 ++-- config.paperforward.yaml | 16 +++++--- config.yaml | 12 ++++-- .../2026-07-19-keel-broker-port-design.md | 5 +++ ...6-07-19-keel-broker-subscription-design.md | 6 +++ .../2026-08-21-v0.11-plus-roadmap-prd.md | 6 +++ .../2026-09-27-accumulation-policy-design.md | 6 +++ keel/research/significance.py | 7 ++-- keel/research/throughput.py | 7 ++-- keel/templates/config.live.yaml | 12 ++++-- keel/templates/config.yaml | 12 ++++-- packages/keel-core/keel_core/config.py | 14 ++++--- tests/test_rail14_is_a_buy_cap.py | 41 +++++++++++++++---- 13 files changed, 113 insertions(+), 40 deletions(-) diff --git a/config.paper-hourly.yaml b/config.paper-hourly.yaml index 2c685229..e4cd68e8 100644 --- a/config.paper-hourly.yaml +++ b/config.paper-hourly.yaml @@ -84,7 +84,7 @@ caps: # this profile's evidence is partly ABOUT rail behaviour (rail vetoes are admissible # evidence), so silently clamping sizing here would bias exactly the thing being measured. # Deliberately NOT raised to admit this profile's proposals. Rail 14 caps MONTHLY buy - # notional at the fee-free allowance ($500 at Basic), so a single ~$4,212 proposal is 8.4x + # notional at the monthly buy cap ($500 at Basic), so a single ~$4,212 proposal is 8.4x # the entire month -- widening the exposure cap admits nothing and only hides the sizing # mismatch that `starting_equity_usd` below actually fixes. max_exposure_usd: 5000 @@ -144,12 +144,15 @@ paper: # amount. Changed from 10000 (#426). # # The synthetic $10k made this profile unable to record anything. It inherits the REAL - # account's subscription allowance -- rail 14, attested Basic, $500/month of fee-free buy - # notional -- while sizing against an account 23x larger than the real one (~$450). At + # account's subscription allowance -- rail 14, attested Basic, $500/month buy + # cap -- while sizing against an account 23x larger than the real one (~$450). At # risk_pct 1% and the measured 2.40% median hourly stop, f_N = risk_pct/s = 0.42, so a # typical proposal was ~$4,212: 8.4x the whole monthly allowance. Every entry was refused. # Over 60 cycles the profile detected 15 setups and recorded 0 trades. # + # (#836, 2026-09-27: the "fee-free regime" below does not exist for keel's orders -- Advanced + # Trade charges its taker fee inside the cap too -- so these rules are losing throughout. The + # reasoning is kept as written.) # Seeding from real equity keeps every proposal inside the fee-free regime, which is also # the ONLY regime where these rules are not structurally losing: inside the allowance # kappa = 0.04R and p_be(b=6) = 14.9%, against a reconstructed 14.9% win rate; outside it diff --git a/config.paperforward.yaml b/config.paperforward.yaml index 44648360..68d68203 100644 --- a/config.paperforward.yaml +++ b/config.paperforward.yaml @@ -28,7 +28,8 @@ risk_pct: 0.01 caps: # max_per_order_usd / max_per_day_usd are OPTIONAL internal RISK limits, not real Coinbase - # limits -- Coinbase One's only subscription constraint is monthly fee-free trading VOLUME + # limits -- Coinbase One's only subscription constraint is a monthly zero-fee trading VOLUME -- on the + # main Coinbase platform only, not Advanced Trade where keel trades (#836) -- # (see `subscription:` below). They default to a non-binding $1B when omitted (Issue #85); # set explicit values here only if you want an extra per-order/per-day risk ceiling tighter # than the exposure/concentration caps below. Left at their non-binding default here so @@ -78,8 +79,8 @@ paper: # paper blocks are equal -- the profiles are meant to differ in CADENCE and nothing else. # # A synthetic account larger than the real one makes a paper profile propose orders the real - # deployment could never fund. Rail 14 caps monthly buy notional at the venue's fee-free - # allowance ($500 at the attested Basic tier), and that allowance is inherited from the REAL + # deployment could never fund. Rail 14 caps monthly buy notional at the venue's monthly + # buy cap ($500 at the attested Basic tier; a cap, not a fee waiver -- #836), and that allowance is inherited from the REAL # account whatever the synthetic equity says. At $10k synthetic and the measured 13.86% # median daily stop, f_N = 0.07 gives ~$722 per trade -- 1.4x the whole monthly allowance. starting_equity_usd: 0 @@ -94,7 +95,8 @@ paper: quote_currency: USD subscription: - # The SIMULATOR's assumed fee-free monthly volume. The LIVE rail-14 cap is not set here -- + # The SIMULATOR's assumed monthly volume allowance (priced at zero fee in the tier analysis, + # which is hypothetical for keel's Advanced Trade orders -- #836). The LIVE rail-14 cap is not set here -- # it comes from the attested record: `keel subscription attest --venue coinbase --tier `. assumed_free_volume_usd: 500 # What rail 14 permits on a venue that is unattested, suspect, lapsed, or overdue. @@ -102,11 +104,13 @@ subscription: unsubscribed_allowance_usd: 0 pacing: opportunistic # opportunistic (monthly cap only) | even_daily (also paces per business day) -# Coinbase One subscription tiers (Issue #86) -- fee-free monthly TRADING VOLUME (buys + sells) +# Coinbase One subscription tiers (Issue #86) -- zero-fee monthly TRADING VOLUME (buys + sells) +# on the main Coinbase platform. NOT Advanced Trade, where keel's orders pay maker/taker fees +# (#836): the sim's within-cap rows are hypothetical for keel. # allowance per tier, used by `keel simulate`'s tier/fee analysis matrix to compare staying # within a tier's free volume (throttled, 0 trading fees, but you still pay the subscription) # against trading freely and paying the taker fee on volume EXCEEDING it. -# free_volume_usd: null means unlimited (Premium -- always fee-free, no cap to exceed). +# free_volume_usd: null means unlimited (Premium -- no cap to exceed). tiers: - name: Basic free_volume_usd: 500 diff --git a/config.yaml b/config.yaml index 446f0cd3..40df9c0f 100644 --- a/config.yaml +++ b/config.yaml @@ -28,7 +28,8 @@ risk_pct: 0.01 caps: # max_per_order_usd / max_per_day_usd are OPTIONAL internal RISK limits, not real Coinbase - # limits -- Coinbase One's only subscription constraint is monthly fee-free trading VOLUME + # limits -- Coinbase One's only subscription constraint is a monthly zero-fee trading VOLUME -- on the + # main Coinbase platform only, not Advanced Trade where keel trades (#836) -- # (see `subscription:` below). They default to a non-binding $1B when omitted (Issue #85); # set explicit values here only if you want an extra per-order/per-day risk ceiling tighter # than the exposure/concentration caps below. Left at their non-binding default here so @@ -102,7 +103,8 @@ settlement_currencies: - USDC subscription: - # The SIMULATOR's assumed fee-free monthly volume. The LIVE rail-14 cap is not set here -- + # The SIMULATOR's assumed monthly volume allowance (priced at zero fee in the tier analysis, + # which is hypothetical for keel's Advanced Trade orders -- #836). The LIVE rail-14 cap is not set here -- # it comes from the attested record: `keel subscription attest --venue coinbase --tier `. assumed_free_volume_usd: 500 # What rail 14 permits on a venue that is unattested, suspect, lapsed, or overdue. @@ -110,11 +112,13 @@ subscription: unsubscribed_allowance_usd: 0 pacing: opportunistic # opportunistic (monthly cap only) | even_daily (also paces per business day) -# Coinbase One subscription tiers (Issue #86) -- fee-free monthly TRADING VOLUME (buys + sells) +# Coinbase One subscription tiers (Issue #86) -- zero-fee monthly TRADING VOLUME (buys + sells) +# on the main Coinbase platform. NOT Advanced Trade, where keel's orders pay maker/taker fees +# (#836): the sim's within-cap rows are hypothetical for keel. # allowance per tier, used by `keel simulate`'s tier/fee analysis matrix to compare staying # within a tier's free volume (throttled, 0 trading fees, but you still pay the subscription) # against trading freely and paying the taker fee on volume EXCEEDING it. -# free_volume_usd: null means unlimited (Premium -- always fee-free, no cap to exceed). +# free_volume_usd: null means unlimited (Premium -- no cap to exceed). tiers: - name: Basic free_volume_usd: 500 diff --git a/docs/superpowers/specs/2026-07-19-keel-broker-port-design.md b/docs/superpowers/specs/2026-07-19-keel-broker-port-design.md index df505097..e7ea0e91 100644 --- a/docs/superpowers/specs/2026-07-19-keel-broker-port-design.md +++ b/docs/superpowers/specs/2026-07-19-keel-broker-port-design.md @@ -1,5 +1,10 @@ # keel — Broker Port & Adapter Plugins — Design Spec +> **Fee note (added 2026-09-27, #836).** Where this design mentions a "claimed fee-free +> allowance", note that for keel's Coinbase orders (Advanced Trade) no fee-free allowance +> exists: they pay the maker/taker schedule inside rail 14's cap. Nothing on this page has +> been rewritten. + **Date:** 2026-07-19 **Status:** Design approved, not yet implemented **Implements:** `2026-07-18-keel-monorepo-architecture-design.md` §7 and §12 step 3. diff --git a/docs/superpowers/specs/2026-07-19-keel-broker-subscription-design.md b/docs/superpowers/specs/2026-07-19-keel-broker-subscription-design.md index df63fcfc..1ee38213 100644 --- a/docs/superpowers/specs/2026-07-19-keel-broker-subscription-design.md +++ b/docs/superpowers/specs/2026-07-19-keel-broker-subscription-design.md @@ -1,5 +1,11 @@ # keel — Per-Venue Subscription Tracking & Lapse Detection — Design Spec +> **Fee note (added 2026-09-27, #836).** This design models Coinbase One's allowance as +> fee-free trading volume. That benefit applies on the main Coinbase platform, not on Advanced +> Trade, where keel places its orders and pays maker/taker fees inside any cap. keel's own live +> account turned out to be exactly the case §"fee charged while the user claims a fee-free +> allowance" describes. Nothing on this page has been rewritten. + **Date:** 2026-07-19 **Status:** Design approved, not yet implemented **Relates to:** `2026-07-19-keel-broker-port-design.md` (resolves its §13 "Fee schedule" open question); diff --git a/docs/superpowers/specs/2026-08-21-v0.11-plus-roadmap-prd.md b/docs/superpowers/specs/2026-08-21-v0.11-plus-roadmap-prd.md index 96bd2da6..29ce1135 100644 --- a/docs/superpowers/specs/2026-08-21-v0.11-plus-roadmap-prd.md +++ b/docs/superpowers/specs/2026-08-21-v0.11-plus-roadmap-prd.md @@ -1,5 +1,11 @@ # PRD — The v0.11+ roadmap: deepen the moat, attack throughput and fees +> **Fee note (added 2026-09-27, #836).** This PRD treats rail 14's allowance as a fee-free +> regime and calls it "the profitability boundary". For keel's orders that premise was wrong: +> they go through Coinbase Advanced Trade, which has no fee-waiver tier (Intro tier, 0.9% taker +> on 2026-09-27, `has_promo_fee: false`), so rail 14 is a monthly buy cap, not a boundary +> between a fee-free and a taxed regime. Nothing on this page has been rewritten. + **Status:** accepted · **Date:** 2026-08-21 · **Milestones:** 20 (v0.11), 19 (research), 15 (venues), 18 (distribution) ## The ask diff --git a/docs/superpowers/specs/2026-09-27-accumulation-policy-design.md b/docs/superpowers/specs/2026-09-27-accumulation-policy-design.md index 8dfbc6c6..60310bfc 100644 --- a/docs/superpowers/specs/2026-09-27-accumulation-policy-design.md +++ b/docs/superpowers/specs/2026-09-27-accumulation-policy-design.md @@ -1,5 +1,11 @@ # Accumulation policy: bounded value averaging and band rebalancing vs static DCA: design +> **Fee note (added 2026-09-27, #836).** D2/D3's "allowance-aware" fee model prices buys +> fee-free up to $500 a month. For keel's orders that regime is hypothetical: they go through +> Coinbase Advanced Trade, which charges its maker/taker schedule on every order. The design's +> flat-taker sensitivity run is the one that matches live; the resulting record's verdict is +> the same under both. Nothing on this page has been rewritten. + **Date:** 2026-09-27 · **Issue:** #831 · **Status:** DRAFT, revision 2 (selective trimming, band floor, dual bootstrap blocks), for review. No code or simulation runs until this is approved. §9 lists the decisions the review has to make. diff --git a/keel/research/significance.py b/keel/research/significance.py index 1fb044b8..6ccb2810 100644 --- a/keel/research/significance.py +++ b/keel/research/significance.py @@ -49,9 +49,10 @@ #: The two fee regimes the reconstruction is priced at. Labels, not truth: the driver threads #: the deployment's own `config.fees.taker_pct` when it has one, and these constants stand in #: for callers that genuinely have no config (library use, tests). `outside_allowance_taker` -#: matches `backtest.TAKER_FEE_PCT` (120 bp, both legs); `inside_allowance_fee_free` is the -#: rail-14 allowance regime where the venue charges nothing on monthly BUY notional up to the -#: cap -- the profitability boundary, not a budget to spend. +#: matches `backtest.TAKER_FEE_PCT` (120 bp, both legs); `inside_allowance_fee_free` is a +#: HYPOTHETICAL zero-fee regime. It was modelled as the rail-14 cap where the venue charges +#: nothing, but keel's Advanced Trade orders pay the taker fee inside the cap too (#836); the +#: key keeps its historical name so recorded results stay comparable. FEE_REGIMES: dict[str, Decimal] = { "outside_allowance_taker": Decimal("0.012"), "inside_allowance_fee_free": Decimal("0"), diff --git a/keel/research/throughput.py b/keel/research/throughput.py index e9d23855..cbab25c7 100644 --- a/keel/research/throughput.py +++ b/keel/research/throughput.py @@ -5,9 +5,10 @@ everywhere else -- report the truth, refuse to flatter: * **Throughput.** A venue's attested monthly buy cap (rail 14) caps monthly BUY - notional, and the cross-verification showed that cap is the *profitability - boundary*: inside it the reconstructed rules sit at break-even, outside it the - taker fee decides. So the honest throughput of a venue is + notional. (The cross-verification once called that cap the *profitability + boundary*, on the premise that buys inside it were fee-free; on Advanced Trade they + are not -- keel's orders pay the taker fee inside the cap too, #836.) So the honest + throughput of a venue is ``min(expected signals, allowance / mean trade notional)`` -- and at the measured numbers (a $4,212 mean hourly proposal against a $500/month Basic allowance) that is a fraction of a trade per month, which is exactly what the report must say diff --git a/keel/templates/config.live.yaml b/keel/templates/config.live.yaml index 69881852..db00c878 100644 --- a/keel/templates/config.live.yaml +++ b/keel/templates/config.live.yaml @@ -38,7 +38,8 @@ risk_pct: 0.01 caps: # max_per_order_usd / max_per_day_usd are OPTIONAL internal RISK limits, not real Coinbase - # limits -- Coinbase One's only subscription constraint is monthly fee-free trading VOLUME + # limits -- Coinbase One's only subscription constraint is a monthly zero-fee trading VOLUME -- on the + # main Coinbase platform only, not Advanced Trade where keel trades (#836) -- # (see `subscription:` below). They default to a non-binding $1B when omitted (Issue #85); # set explicit values here only if you want an extra per-order/per-day risk ceiling tighter # than the exposure/concentration caps below. Left at their non-binding default here so @@ -113,7 +114,8 @@ settlement_currencies: - USDC subscription: - # The SIMULATOR's assumed fee-free monthly volume. The LIVE rail-14 cap is not set here -- + # The SIMULATOR's assumed monthly volume allowance (priced at zero fee in the tier analysis, + # which is hypothetical for keel's Advanced Trade orders -- #836). The LIVE rail-14 cap is not set here -- # it comes from the attested record: `keel subscription attest --venue coinbase --tier `. assumed_free_volume_usd: 500 # What rail 14 permits on a venue that is unattested, suspect, lapsed, or overdue. @@ -121,11 +123,13 @@ subscription: unsubscribed_allowance_usd: 0 pacing: opportunistic # opportunistic (monthly cap only) | even_daily (also paces per business day) -# Coinbase One subscription tiers (Issue #86) -- fee-free monthly TRADING VOLUME (buys + sells) +# Coinbase One subscription tiers (Issue #86) -- zero-fee monthly TRADING VOLUME (buys + sells) +# on the main Coinbase platform. NOT Advanced Trade, where keel's orders pay maker/taker fees +# (#836): the sim's within-cap rows are hypothetical for keel. # allowance per tier, used by `keel simulate`'s tier/fee analysis matrix to compare staying # within a tier's free volume (throttled, 0 trading fees, but you still pay the subscription) # against trading freely and paying the taker fee on volume EXCEEDING it. -# free_volume_usd: null means unlimited (Premium -- always fee-free, no cap to exceed). +# free_volume_usd: null means unlimited (Premium -- no cap to exceed). tiers: - name: Basic free_volume_usd: 500 diff --git a/keel/templates/config.yaml b/keel/templates/config.yaml index 446f0cd3..40df9c0f 100644 --- a/keel/templates/config.yaml +++ b/keel/templates/config.yaml @@ -28,7 +28,8 @@ risk_pct: 0.01 caps: # max_per_order_usd / max_per_day_usd are OPTIONAL internal RISK limits, not real Coinbase - # limits -- Coinbase One's only subscription constraint is monthly fee-free trading VOLUME + # limits -- Coinbase One's only subscription constraint is a monthly zero-fee trading VOLUME -- on the + # main Coinbase platform only, not Advanced Trade where keel trades (#836) -- # (see `subscription:` below). They default to a non-binding $1B when omitted (Issue #85); # set explicit values here only if you want an extra per-order/per-day risk ceiling tighter # than the exposure/concentration caps below. Left at their non-binding default here so @@ -102,7 +103,8 @@ settlement_currencies: - USDC subscription: - # The SIMULATOR's assumed fee-free monthly volume. The LIVE rail-14 cap is not set here -- + # The SIMULATOR's assumed monthly volume allowance (priced at zero fee in the tier analysis, + # which is hypothetical for keel's Advanced Trade orders -- #836). The LIVE rail-14 cap is not set here -- # it comes from the attested record: `keel subscription attest --venue coinbase --tier `. assumed_free_volume_usd: 500 # What rail 14 permits on a venue that is unattested, suspect, lapsed, or overdue. @@ -110,11 +112,13 @@ subscription: unsubscribed_allowance_usd: 0 pacing: opportunistic # opportunistic (monthly cap only) | even_daily (also paces per business day) -# Coinbase One subscription tiers (Issue #86) -- fee-free monthly TRADING VOLUME (buys + sells) +# Coinbase One subscription tiers (Issue #86) -- zero-fee monthly TRADING VOLUME (buys + sells) +# on the main Coinbase platform. NOT Advanced Trade, where keel's orders pay maker/taker fees +# (#836): the sim's within-cap rows are hypothetical for keel. # allowance per tier, used by `keel simulate`'s tier/fee analysis matrix to compare staying # within a tier's free volume (throttled, 0 trading fees, but you still pay the subscription) # against trading freely and paying the taker fee on volume EXCEEDING it. -# free_volume_usd: null means unlimited (Premium -- always fee-free, no cap to exceed). +# free_volume_usd: null means unlimited (Premium -- no cap to exceed). tiers: - name: Basic free_volume_usd: 500 diff --git a/packages/keel-core/keel_core/config.py b/packages/keel-core/keel_core/config.py index 42e67f22..ed73acf3 100644 --- a/packages/keel-core/keel_core/config.py +++ b/packages/keel-core/keel_core/config.py @@ -209,12 +209,14 @@ class SubscriptionConfig: @dataclass(frozen=True) class TierConfig: """One Coinbase One subscription tier (Issue #86) -- `sim.tiers`/`keel simulate`'s tier/fee - analysis matrix compares staying WITHIN a tier's fee-free monthly trading-volume allowance - against trading freely and paying the taker fee on volume EXCEEDING it. - - `free_volume_usd is None` means an UNLIMITED fee-free allowance (Premium) -- there is no - volume beyond which fees apply, so within-cap and over-cap analysis collapse to the same - (always fee-free) result for that tier. + analysis matrix compares staying WITHIN a tier's monthly trading-volume allowance (priced + at zero fee) against trading freely and paying the taker fee on volume EXCEEDING it. The + zero-fee side models Coinbase One on the main Coinbase platform; it is HYPOTHETICAL for + keel's orders, which go through Advanced Trade and pay its maker/taker fees (#836). + + `free_volume_usd is None` means an UNLIMITED allowance (Premium) -- there is no volume + beyond which the modelled fee applies, so within-cap and over-cap analysis collapse to the + same result for that tier. """ name: str diff --git a/tests/test_rail14_is_a_buy_cap.py b/tests/test_rail14_is_a_buy_cap.py index 4123d9d8..8d1a342b 100644 --- a/tests/test_rail14_is_a_buy_cap.py +++ b/tests/test_rail14_is_a_buy_cap.py @@ -30,14 +30,21 @@ _ROOT = Path(__file__).resolve().parent.parent _RAIL_DOC = _ROOT / "docs/rails/rail-14-subscription-allowance.md" _FEE_NOTE = "#836" -#: A sentence asserting the venue waives fees inside the cap. Mentioning the words to say they -#: do NOT apply is allowed; claiming them is not. +#: A sentence asserting the venue waives fees inside the cap. Matched against NORMALISED text +#: (`_claims`): markdown emphasis and blockquote markers stripped, whitespace collapsed -- the +#: pre-#836 doc split the phrase across a line and wrapped it in `**`, which a raw regex misses. _CLAIM = re.compile( - r"lets the account trade taker-fee-free|trade taker-fee-free|fee-free monthly volume allowance", + r"lets the account trade taker-fee-free|fee-free monthly volume allowance" + r"|the monthly volume the venue .{0,60} lets the account trade", re.I, ) +def _claims(text: str) -> bool: + flat = re.sub(r"\s+", " ", re.sub(r"[*_`]|^\s*>", "", text, flags=re.M)) + return bool(_CLAIM.search(flat)) + + def test_subscription_set_help_calls_it_a_buy_cap_not_a_fee_waiver() -> None: result = CliRunner().invoke(cli, ["subscription", "set", "--help"]) assert result.exit_code == 0, result.output @@ -77,7 +84,7 @@ def test_the_tier_section_says_its_within_cap_rows_are_hypothetical_on_advanced_ def test_the_rail_14_doc_no_longer_claims_fees_are_waived() -> None: doc = _RAIL_DOC.read_text(encoding="utf-8") - assert not _CLAIM.search(doc), "the rail 14 doc still claims the venue waives fees" + assert not _claims(doc), "the rail 14 doc still claims the venue waives fees" assert "buy cap" in doc.lower() assert _FEE_NOTE in doc @@ -85,9 +92,11 @@ def test_the_rail_14_doc_no_longer_claims_fees_are_waived() -> None: def test_every_record_that_described_a_fee_free_regime_carries_the_fee_note() -> None: """Records are appended to, never rewritten. A record that priced or described a fee-free regime keeps its text and gains a dated note pointing at #836.""" - records = sorted((_ROOT / "docs/experiments").glob("*.md")) + sorted( - (_ROOT / "docs/research").glob("*.md") - ) + records = [ + path + for folder in ("docs/experiments", "docs/research", "docs/superpowers/specs") + for path in sorted((_ROOT / folder).glob("*.md")) + ] missing = [ path.name for path in records @@ -97,3 +106,21 @@ def test_every_record_that_described_a_fee_free_regime_carries_the_fee_note() -> assert not missing, "records describing a fee-free regime without the #836 note:\n " + ( "\n ".join(missing) ) + + +#: The paragraph this PR retracted, verbatim from the pre-#836 doc, including its line break and +#: its `**…**` emphasis. The detector must catch it, or the doc test above passes by vacuum. +_RETRACTED = ( + "The cap is the subscription tier's `free_volume_usd`: the monthly volume the venue " + "(Coinbase\n" + "Advanced Trade, at time of writing) lets the account trade **taker-fee-free**. That makes " + "the\n" + "rail's economics, not just its accounting:" +) + + +def test_the_claim_detector_catches_the_retracted_paragraph() -> None: + """Review of #837: the first `_CLAIM` never matched the real doc (a newline split the phrase + and `**` wrapped it), so the doc test could not fail. Pinned against the actual old text.""" + assert _claims(_RETRACTED) + assert not _claims("keel's orders pay the venue's maker/taker fee inside the buy cap") From af811ebc8f02f4024c880fb2dc58b119058394b9 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Sun, 27 Sep 2026 09:45:38 -0400 Subject: [PATCH 3/4] fix(rails): address #837 round 2 -- the fees comment no longer limits fees to beyond a free allowance (#838) The fees: block in both setup templates, config.yaml and config.paperforward.yaml, and the FeesConfig docstring, said Advanced Trade fees applied only to volume beyond a tier's free allowance. They apply to every keel order. The comment now says so, and that the account measured 0.9% / 0.5% on 2026-09-27 while the sim keeps 1.2% / 0.6% as its conservative figure (values unchanged). The tiers comment, which the first fix split mid-sentence, is rewritten as one paragraph. A test pins the templates keel setup writes; it fails on the pre-#836 template. Co-Authored-By: Claude Opus 5.5 --- config.paperforward.yaml | 18 ++++++++++-------- config.yaml | 18 ++++++++++-------- keel/templates/config.live.yaml | 18 ++++++++++-------- keel/templates/config.yaml | 18 ++++++++++-------- packages/keel-core/keel_core/config.py | 10 ++++++---- tests/test_rail14_is_a_buy_cap.py | 14 ++++++++++++++ 6 files changed, 60 insertions(+), 36 deletions(-) diff --git a/config.paperforward.yaml b/config.paperforward.yaml index 68d68203..f3fd50e4 100644 --- a/config.paperforward.yaml +++ b/config.paperforward.yaml @@ -104,12 +104,12 @@ subscription: unsubscribed_allowance_usd: 0 pacing: opportunistic # opportunistic (monthly cap only) | even_daily (also paces per business day) -# Coinbase One subscription tiers (Issue #86) -- zero-fee monthly TRADING VOLUME (buys + sells) -# on the main Coinbase platform. NOT Advanced Trade, where keel's orders pay maker/taker fees -# (#836): the sim's within-cap rows are hypothetical for keel. -# allowance per tier, used by `keel simulate`'s tier/fee analysis matrix to compare staying -# within a tier's free volume (throttled, 0 trading fees, but you still pay the subscription) -# against trading freely and paying the taker fee on volume EXCEEDING it. +# Coinbase One subscription tiers (Issue #86): each tier's monthly TRADING VOLUME (buys + sells) +# allowance, used by `keel simulate`'s tier/fee analysis matrix to compare staying within a +# tier's allowance (throttled, 0 trading fees, but you still pay the subscription) against +# trading freely and paying the taker fee on volume EXCEEDING it. That zero-fee allowance is a +# main-Coinbase-platform benefit, NOT Advanced Trade, where keel's orders pay maker/taker fees +# (#836) -- so the within-cap rows are hypothetical for keel. # free_volume_usd: null means unlimited (Premium -- no cap to exceed). tiers: - name: Basic @@ -122,8 +122,10 @@ tiers: free_volume_usd: null subscription_usd_month: 299.99 -# Coinbase Advanced trading fees applied to volume beyond a tier's free allowance, for a -# <$1k-30d-volume account (Coinbase's published fee schedule). taker_pct is the sim's default -- +# Coinbase Advanced trading fees. They apply to EVERY keel order -- there is no free allowance +# on Advanced Trade (#836). On 2026-09-27 the account measured the Intro tier at 0.9% taker / +# 0.5% maker; these values are kept at 1.2% / 0.6% as the conservative sim figure, so recorded +# results stay comparable. taker_pct is the sim's default -- # it fills market-style at next-bar open; maker_pct is exposed for a caller that wants to model # limit-order fills instead. fees: diff --git a/config.yaml b/config.yaml index 40df9c0f..34c4b466 100644 --- a/config.yaml +++ b/config.yaml @@ -112,12 +112,12 @@ subscription: unsubscribed_allowance_usd: 0 pacing: opportunistic # opportunistic (monthly cap only) | even_daily (also paces per business day) -# Coinbase One subscription tiers (Issue #86) -- zero-fee monthly TRADING VOLUME (buys + sells) -# on the main Coinbase platform. NOT Advanced Trade, where keel's orders pay maker/taker fees -# (#836): the sim's within-cap rows are hypothetical for keel. -# allowance per tier, used by `keel simulate`'s tier/fee analysis matrix to compare staying -# within a tier's free volume (throttled, 0 trading fees, but you still pay the subscription) -# against trading freely and paying the taker fee on volume EXCEEDING it. +# Coinbase One subscription tiers (Issue #86): each tier's monthly TRADING VOLUME (buys + sells) +# allowance, used by `keel simulate`'s tier/fee analysis matrix to compare staying within a +# tier's allowance (throttled, 0 trading fees, but you still pay the subscription) against +# trading freely and paying the taker fee on volume EXCEEDING it. That zero-fee allowance is a +# main-Coinbase-platform benefit, NOT Advanced Trade, where keel's orders pay maker/taker fees +# (#836) -- so the within-cap rows are hypothetical for keel. # free_volume_usd: null means unlimited (Premium -- no cap to exceed). tiers: - name: Basic @@ -130,8 +130,10 @@ tiers: free_volume_usd: null subscription_usd_month: 299.99 -# Coinbase Advanced trading fees applied to volume beyond a tier's free allowance, for a -# <$1k-30d-volume account (Coinbase's published fee schedule). taker_pct is the sim's default -- +# Coinbase Advanced trading fees. They apply to EVERY keel order -- there is no free allowance +# on Advanced Trade (#836). On 2026-09-27 the account measured the Intro tier at 0.9% taker / +# 0.5% maker; these values are kept at 1.2% / 0.6% as the conservative sim figure, so recorded +# results stay comparable. taker_pct is the sim's default -- # it fills market-style at next-bar open; maker_pct is exposed for a caller that wants to model # limit-order fills instead. fees: diff --git a/keel/templates/config.live.yaml b/keel/templates/config.live.yaml index db00c878..17ce8b33 100644 --- a/keel/templates/config.live.yaml +++ b/keel/templates/config.live.yaml @@ -123,12 +123,12 @@ subscription: unsubscribed_allowance_usd: 0 pacing: opportunistic # opportunistic (monthly cap only) | even_daily (also paces per business day) -# Coinbase One subscription tiers (Issue #86) -- zero-fee monthly TRADING VOLUME (buys + sells) -# on the main Coinbase platform. NOT Advanced Trade, where keel's orders pay maker/taker fees -# (#836): the sim's within-cap rows are hypothetical for keel. -# allowance per tier, used by `keel simulate`'s tier/fee analysis matrix to compare staying -# within a tier's free volume (throttled, 0 trading fees, but you still pay the subscription) -# against trading freely and paying the taker fee on volume EXCEEDING it. +# Coinbase One subscription tiers (Issue #86): each tier's monthly TRADING VOLUME (buys + sells) +# allowance, used by `keel simulate`'s tier/fee analysis matrix to compare staying within a +# tier's allowance (throttled, 0 trading fees, but you still pay the subscription) against +# trading freely and paying the taker fee on volume EXCEEDING it. That zero-fee allowance is a +# main-Coinbase-platform benefit, NOT Advanced Trade, where keel's orders pay maker/taker fees +# (#836) -- so the within-cap rows are hypothetical for keel. # free_volume_usd: null means unlimited (Premium -- no cap to exceed). tiers: - name: Basic @@ -141,8 +141,10 @@ tiers: free_volume_usd: null subscription_usd_month: 299.99 -# Coinbase Advanced trading fees applied to volume beyond a tier's free allowance, for a -# <$1k-30d-volume account (Coinbase's published fee schedule). taker_pct is the sim's default -- +# Coinbase Advanced trading fees. They apply to EVERY keel order -- there is no free allowance +# on Advanced Trade (#836). On 2026-09-27 the account measured the Intro tier at 0.9% taker / +# 0.5% maker; these values are kept at 1.2% / 0.6% as the conservative sim figure, so recorded +# results stay comparable. taker_pct is the sim's default -- # it fills market-style at next-bar open; maker_pct is exposed for a caller that wants to model # limit-order fills instead. fees: diff --git a/keel/templates/config.yaml b/keel/templates/config.yaml index 40df9c0f..34c4b466 100644 --- a/keel/templates/config.yaml +++ b/keel/templates/config.yaml @@ -112,12 +112,12 @@ subscription: unsubscribed_allowance_usd: 0 pacing: opportunistic # opportunistic (monthly cap only) | even_daily (also paces per business day) -# Coinbase One subscription tiers (Issue #86) -- zero-fee monthly TRADING VOLUME (buys + sells) -# on the main Coinbase platform. NOT Advanced Trade, where keel's orders pay maker/taker fees -# (#836): the sim's within-cap rows are hypothetical for keel. -# allowance per tier, used by `keel simulate`'s tier/fee analysis matrix to compare staying -# within a tier's free volume (throttled, 0 trading fees, but you still pay the subscription) -# against trading freely and paying the taker fee on volume EXCEEDING it. +# Coinbase One subscription tiers (Issue #86): each tier's monthly TRADING VOLUME (buys + sells) +# allowance, used by `keel simulate`'s tier/fee analysis matrix to compare staying within a +# tier's allowance (throttled, 0 trading fees, but you still pay the subscription) against +# trading freely and paying the taker fee on volume EXCEEDING it. That zero-fee allowance is a +# main-Coinbase-platform benefit, NOT Advanced Trade, where keel's orders pay maker/taker fees +# (#836) -- so the within-cap rows are hypothetical for keel. # free_volume_usd: null means unlimited (Premium -- no cap to exceed). tiers: - name: Basic @@ -130,8 +130,10 @@ tiers: free_volume_usd: null subscription_usd_month: 299.99 -# Coinbase Advanced trading fees applied to volume beyond a tier's free allowance, for a -# <$1k-30d-volume account (Coinbase's published fee schedule). taker_pct is the sim's default -- +# Coinbase Advanced trading fees. They apply to EVERY keel order -- there is no free allowance +# on Advanced Trade (#836). On 2026-09-27 the account measured the Intro tier at 0.9% taker / +# 0.5% maker; these values are kept at 1.2% / 0.6% as the conservative sim figure, so recorded +# results stay comparable. taker_pct is the sim's default -- # it fills market-style at next-bar open; maker_pct is exposed for a caller that wants to model # limit-order fills instead. fees: diff --git a/packages/keel-core/keel_core/config.py b/packages/keel-core/keel_core/config.py index ed73acf3..ecc2755a 100644 --- a/packages/keel-core/keel_core/config.py +++ b/packages/keel-core/keel_core/config.py @@ -244,10 +244,12 @@ class LoggingConfig: @dataclass(frozen=True) class FeesConfig: - """Coinbase Advanced trading fees applied to volume beyond a tier's free allowance - (Issue #86) -- the `<$1k-30d-volume` account tier's published rate. `taker_pct` is the sim's - default fee-schedule rate (fills are modeled market-style, at next-bar open); `maker_pct` is - exposed for a caller that wants to model limit-order fills instead.""" + """Coinbase Advanced trading fees (Issue #86). They apply to EVERY keel order: Advanced Trade + has no free allowance (#836). The defaults are a conservative sim figure -- the account + measured the Intro tier at 0.9% taker / 0.5% maker on 2026-09-27 -- kept so recorded results + stay comparable. `taker_pct` is the sim's default fee-schedule rate (fills are modeled + market-style, at next-bar open); `maker_pct` is exposed for a caller that wants to model + limit-order fills instead.""" taker_pct: Decimal = Decimal("0.012") maker_pct: Decimal = Decimal("0.006") diff --git a/tests/test_rail14_is_a_buy_cap.py b/tests/test_rail14_is_a_buy_cap.py index 8d1a342b..c5893a23 100644 --- a/tests/test_rail14_is_a_buy_cap.py +++ b/tests/test_rail14_is_a_buy_cap.py @@ -124,3 +124,17 @@ def test_the_claim_detector_catches_the_retracted_paragraph() -> None: and `**` wrapped it), so the doc test could not fail. Pinned against the actual old text.""" assert _claims(_RETRACTED) assert not _claims("keel's orders pay the venue's maker/taker fee inside the buy cap") + + +def test_the_config_templates_keel_setup_writes_make_no_free_allowance_claim() -> None: + """Review round 2 of #837: `keel setup` copies these templates into a new deployment, so a + fee-free claim here would spread to every install. The `fees:` comment used to say Advanced + Trade fees applied only "to volume beyond a tier's free allowance".""" + for name in ("config.live.yaml", "config.yaml"): + text = (_ROOT / "keel/templates" / name).read_text(encoding="utf-8") + # YAML comments wrap across lines; drop each line's `#` marker before flattening. + flat = " ".join(re.sub(r"^\s*#", " ", text, flags=re.M).split()).lower() + assert "beyond a tier's free allowance" not in flat, name + assert "always fee-free" not in flat, name + assert "no free allowance on advanced trade" in flat, name + assert _FEE_NOTE in text, name From 027b019192c3661218e790146cf3c3e66524ab49 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Sun, 27 Sep 2026 09:52:01 -0400 Subject: [PATCH 4/4] docs(spec): fee note on the superseded 2026-07-14 design, which assumed Coinbase One covered keel's trades (#836) Held from #837's round-3 review: the original design spec says the Coinbase One zero-fee benefit covers the trades this tool places. It gains the same dated, appended note as the other specs. Co-Authored-By: Claude Opus 5.5 --- docs/superpowers/specs/2026-07-14-keel-design.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/superpowers/specs/2026-07-14-keel-design.md b/docs/superpowers/specs/2026-07-14-keel-design.md index 37dbeefc..09a32638 100644 --- a/docs/superpowers/specs/2026-07-14-keel-design.md +++ b/docs/superpowers/specs/2026-07-14-keel-design.md @@ -1,5 +1,11 @@ # keel — Design Spec +> **Fee note (added 2026-09-27, #836).** This design assumes "the Coinbase One zero-fee benefit +> covers the trades this tool places". It does not: keel places its orders through Coinbase +> Advanced Trade, which charges its maker/taker schedule on every order (0.9% taker on the +> account's Intro tier, 2026-09-27); Coinbase One's zero trading fees apply on the main Coinbase +> platform only. Nothing on this page has been rewritten. + **Date:** 2026-07-14 **Status:** ⚠️ SUPERSEDED by `2026-07-15-keel-autotrade-design.md` — that spec extends this preview-only advisory MVP into an autonomous auto-trading agent (SQLite tracking, rule engine, safety rails). This doc remains the record of the original advisory design. **Author:** Elmehdi Aitbrahim (with Claude)