Skip to content
Buildeon Labs

Pre-registration

The complete design document for Deterministic vs Jev-gated spot strategies: question, hypotheses, verdict rule, power, mechanics and disclaimers, as written in advance.

Pre-T0 draft — the frozen version will be published with its hash at the M1 freeze.

PRD SHA-256 (original file)
93050777a3c45cdd8777cb29ee7d21d8b391e5f6c8eb937f6aa8a0177ad8a614
Skill pack commit
493ffb27de38d986c1337f504f14d24bf9779a8b
PRD last changed
Source file
Experiment PRD (private), sanitized for publication
What was changed for publication

The SHA-256 above is of the original file at that commit, so it is the pre-registration's fingerprint. The text below is that file with only private links and paths removed; no number, rule or claim is changed. A deterministic, tested script (scripts/sanitize-prd.py) applies these rules, in order:

  • API keys, tokens and private-key blocks are replaced by [redacted secret] (0 changes)
  • Links into private repositories keep their text and lose the URL (0 changes)
  • Bare URLs of private repositories become "a private repository" (0 changes)
  • Private repository names, and a file path cited with them, become "a private repository" (1 change)
  • Server file paths (/opt, /home, /root, /srv, /tmp, /var, /mnt, /etc, ~/) become [private path] (0 changes)
  • Loopback, link-local and private IP addresses (with any port) become [private address] (1 change)
  • Issue and PR numbers of the private skill pack become plain text, e.g. "pack issue #36" (77 changes)
  • A bare (#N) reference to the pack becomes (pack #N) (2 changes)

SHA-256 of the published text: 3c43f447e76c0840fa73878702742a5f26e78da3e0f722958c805ba9b2ae2531

The document is the experiment's technical specification. Its trading vocabulary describes the paper bots' simulated orders and is not advice.

PRD: Deterministic vs LLM-gated halal spot trading (paper A/B)

Status: draft, awaiting owner review. Amendment 1 (2026-10-07) applied; see the note below and §14. Errata (2026-10-07 and 2026-10-08), the T0 move of 2026-10-19 and the decision-source amendment of 2026-10-21 (TypeSafe Jev, approve or skip only, T0 2026-10-26) are in §14. Last reviewed: 2026-10-21 Owner: Wakqas Ahmed Public surface: labs.buildeon.solutions (one experiment among several Buildeon Labs experiments)

Amendment 1, dated 2026-10-07: the “4+3” design (pack issue #35). It was made before M1 and before T0. No run has started, and the verdict script is not frozen yet (§9), so this is still pre-registration.

  • Two strategies, two arms each, four bots in all:
    • DCA is the primary strategy and the only one counted in the headline. Arm A buys the frozen basket in equal weights once a week. Arm B is offered the same buys, and the LLM may approve, shrink or skip each coin’s tranche (since the 2026-10-21 amendment, §14, a decision-only model may approve or skip only). Unspent budget carries to the coin’s next tranche, and the final tranche invests everything remaining (owner decision, 2026-10-07), so the gate decides when to buy, not whether.
    • TrendCash runs as a labelled control, control (failed validation gate). It gets its own verdict under the existing rules and is excluded from the headline.
  • BTCETHGrid_v3 is dropped (§5, Strategies).
  • Option 3. New strategy families are researched in parallel on the build VPS (pack issue #38), alongside the TrendRider (pack #37) and BinHV45_Regime_5m (pack #43) gate case studies. They are for a later experiment only, never this one.
  • Paper capital is 10,000 USDT per arm, in every arm (owner decisions: 5,000 on 2026-10-07, up from 1,000; 10,000 on 2026-10-08, pack issue #58, §14). The same wallet in all four bots keeps DCA and TrendCash consistent, and it lets the DCA basket rule reach k = 10 (§5, DCA mechanics).
  • Sizing parity with gate 0.4.0. A signal-family arm’s stake is the gate’s own formula, floor(wallet × capital.headroom / max_open_trades), headroom 0.90 unless the strategy pre-registers a lower one. TrendCash pre-registers 0.50, so its stake is 2,500 USDT, and its control status is recorded by gate attempt 3 under gate 0.4.0 (§5, TrendCash control; erratum 2026-10-08, pack issue #58, §14).
  • Unchanged: 90 days, the pinned free Nemotron model (replaced by TypeSafe Jev in the 2026-10-21 amendment, §14), fixed stakes (never “unlimited”) and the lab VPS deployment.

§14 lists every changed rule. §13 lists the implementation work.

1. Problem

Retail “AI trading bots” are sold on claims nobody can check. We want a public, pre-registered answer to one narrow question:

Given the same screened coins, the same strategies, the same capital, and the same 90 days, does letting a decision-only model approve or skip each strategy signal (for DCA: each scheduled buy tranche) beat executing it as-is?

Until the 2026-10-21 amendment (§14) the question was whether a general LLM’s judgement (approve, reject or shrink) beats the schedule. Arm B’s model is now TypeSafe Jev, a hosted decision-only model that answers a fixed approve-or-skip question with probabilities and no free text (§5, LLM provider and budget). So the experiment now tests whether a decision-only model’s timing beats the schedule, not whether an LLM’s does. The names “LLM-gated”, “LLM cost term” and “LLM beyond simple rules” are kept, because they are labels on the dashboard and in the frozen verdict script’s output; for this run they refer to that model.

The owner also wants to use the result for their own investing. Real money is out of scope for this experiment.

2. Return expectations (read before anything else)

  • The owner’s aspiration is up to 25% a year. That is not a success criterion. A 90-day paper run cannot establish an annual return, and nothing in this PRD promises one.
  • Prior evidence points the other way:
    • Owner’s backtests. The owner’s 2026-05 multi-regime backtests of six spot strategies lost money in 16 of 18 strategy-by-regime runs, including 5 of 6 during a +48% BTC rally. Report: a private repository, original sha256 7e278960bede407188ff22c47245386f90d0d36dcf3d64a5db97b724c5be59a3. A sanitized copy, with the recount from its table, is committed at docs/evidence/2026-05-fleet-backtests/ (pack issue #55).
    • Retail outcomes. Most crypto-app users in nearly all economies lost money on their bitcoin holdings over Aug 2015–Dec 2022 [BIS-BULL69-01].
    • LLM traders. An LLM crypto-trading agent did not beat traditional trading signals [CRYPTOTRADE-EMNLP24-01]. In US equities, claimed LLM-strategy advantages faded over longer periods and broader symbol sets [FINSABER-2505-07078-01].
  • Time-series momentum in crypto returns is documented [LIU-TSYVINSKI-RFS21-01] (not yet re-verified against the paper; see SOURCES.md). Holding a stablecoin “cash” state when out of the market is this pack’s design choice, not a finding of that paper.
  • Every arm is also compared against passive Hold and DCA of the same pairs. Beating each other while losing to Hold is a reportable, valid result.
  • For the DCA strategy, arm A is the DCA baseline, run live by a bot. Arm B can only defer part of a coin’s tranche to that coin’s next tranche, and the final tranche invests what remains, so both arms invest the same budget. §3 (DCA family) separates the market’s drift over the deferral weeks from the choice of which weeks to defer.

3. Hypotheses and verdict (pre-registered)

Unit of analysis

There is one verdict per strategy. There is no pooled verdict. A headline may only say “Agentic better on k of n validated strategies”, and it must list each strategy’s verdict. A labelled control (§5, Strategies) gets its own verdict but is excluded from n, k and every headline.

  • After amendment 1 the only counted strategy is DCA, so n = 1.
  • “Validated” covers both routes in PACK_SPEC Principle 3: a gate pass, or the baseline-family checks (§5, Validation of DCA).
  • TrendCash is the labelled control.

Strategies come in two families, and each has its own hypotheses:

  • the signal family (TrendCash): the strategy emits entry and exit signals;
  • the DCA family: a fixed schedule of buys and no exits.

The void conditions are shared, with the DCA adjustments stated in place.

Definitions (both families)

  • Window: [T0, T_end], where T_end = T0 + 90 days, unless truncated by the budget rule (§5, LLM provider). T0 is at 12:00 UTC on any weekday (§5, DCA mechanics): Monday 2026-10-26 12:00 UTC (owner decision, 2026-10-21, pack issue #77; it moved from Friday 2026-10-23, which had replaced Monday 2026-10-19, §14), so T_end is 2027-01-24 12:00 UTC.
  • Paper wallet W0: 10,000 USDT per arm (§5, Capital and fees; erratum 2026-10-08, §14; it was 5,000).
  • Net return R: (equity at T_end − W0) / W0. Equity is marked hourly from the trades database and prices. Open positions are marked at the hourly close net of the exit fee. For arm B, equity is net of that strategy’s LLM spend.
  • Non-fallback decisions: rows with fallback == "none". Fallback rows are not LLM judgements and are excluded from H3 and H3-D.
  • Net: after the fee and fill model shared by all arms (§5, Capital and fees).

Signal family (TrendCash control)

Definitions

  • Veto-only counterfactual B_vo: B’s realised trades with every approved stake rescaled to multiplier 1.0. Spot P&L and fees scale linearly with stake, so P&L_vo = P&L / multiplier, ignoring lot-size rounding. The multiplier is the journal’s applied_multiplier, the one freqtrade actually applied after its stake validation, not the model’s size_multiplier.
    • Selection effect = R(B_vo) − R(A).
    • Sizing effect = R(B) − R(B_vo).
  • Shadow ledger: the per-signal outcome source for H3 (§6).

Hypotheses

  • H1 (primary, return): D1 = R(B) − R(A), on B’s full portfolio. That includes slot-freed trades, and missed slot-blocked trades are not imputed.
    • Pass: D1 ≥ +1.0 percentage point and the 90% CI lower bound > 0.
    • Fail: D1 ≤ −1.0 pp and the 90% CI upper bound < 0.
    • Otherwise undecided.
    • The CI is a stationary block bootstrap of daily (B − A) equity returns (§6, Statistics).
  • H2 (risk, non-inferiority): D2 = MDD(B) − MDD(A), on hourly equity, in percentage points.
    • Pass: D2 ≤ 2.0.
    • Fail: D2 > 2.0.
    • The same figure is reported for B_vo, because shrinking alone lowers drawdown.
  • H3 (veto quality): compares non-fallback, ledger-matched entry decisions.
    • Δ3 = mean net shadow return of vetoed signals − mean net shadow return of approved signals. Both sides come from the shadow ledger, so B’s sizing and exit vetoes do not leak in.
    • Pass: Δ3 < 0, the 90% CI upper bound < 0, and the veto loss rate exceeds arm A’s base loss rate (point estimate). The veto loss rate is the share of vetoed signals with a net loss. The base loss rate is the share of A’s closed trades with a net loss over the window.
    • Fail: the 90% CI lower bound of Δ3 > 0, i.e. vetoed signals did better.
    • Not evaluable: fewer than 20 matched vetoes or fewer than 20 matched approvals, or unmatched vetoes > 20% of non-fallback vetoes.
    • Otherwise undecided.

Verdict rule (signal family, applied in order)

  1. Void: any void condition below holds.
  2. Unavailable (at the readout only; erratum 2026-10-08, §14): no void condition holds, but an arm’s restart and state-change ledger cannot be shown complete over [T0, T_end] (§5, Market and timing). Without it a manual intervention or restart downtime may be missing, so no other verdict is published. The run stays in the public history with its data and the reason.
  3. Agentic better:
    • H1, H2 and H3 all pass;
    • and the selection effect is > 0, so the gain is not only from shrinking positions.
  4. Agentic worse: any of these holds.
    • H1 fails.
    • H2 fails.
    • H3 fails.
  5. Inconclusive: everything else. The dashboard states the reason, for example “H3 not evaluable (12 vetoes)” or “H1 passes on sizing only”. This is the expected outcome at small trade counts.

The rule is symmetric: each hypothesis has its own fail condition, and any single fail reads as “Agentic worse”.

DCA family (amendment 1)

Definitions

  • Tranche (i, j): basket coin i at due time τ_j, for j = 0..12 (§5, DCA mechanics). There are 13k tranches, where k is the basket size: 130 at k = 10. A tranche due after the window end does not exist.

    • Gated tranches are j = 0..11: arm B asks the LLM once for each, so there are 12k LLM decisions (120 at k = 10).
    • The final tranche j = 12 is not gated. Both arms invest everything left in the coin’s budget, with no LLM call (§5, Carry-forward).
  • s: the scheduled per-coin tranche stake (§5, DCA mechanics).

  • Budget b_ij: coin i’s budget at τ_j in an arm, in USDT. b_i0 = s, and b_i,j+1 = s + (b_ij − x_ij), where x_ij is the stake the arm ordered for (i, j). Unspent budget carries to the coin’s next tranche (§5, Carry-forward).

    • x_ij is the ordered stake: the journaled stake the arm passed to freqtrade, freqtrade’s pre-fee stake_amount, with 0 for a skip or a missed tranche. It is not the filled cost.
    • Lot-step truncation. freqtrade 2026.9 truncates a dry-run order’s amount to the market’s lot step [FREQTRADE-EXCHANGE-SOURCE-01], so the filled stake (filled amount × fill price) can be up to one lot step × price below x_ij. For example, a ×0.25 order of 18.845 USDT (0.25 × s at the 10,000 USDT wallet) for BTC can fill short by up to one BTC lot step × price. That bound is the same in USDT at any wallet, so it is a smaller share of a larger order (erratum 2026-10-08, §14; the example was a 9.4225 USDT order, about 3% short). That truncation residue is not carried: it stays idle USDT in that arm, is reported per arm, and goes to the D1 residual.
  • Fee convention. freqtrade’s stake is pre-fee and the entry fee is charged on top, so a fill’s cost with entry fee is filled stake × (1 + f), where f is the fee rate, 0.001 (§5, Capital and fees). Every cost, return and idle-cash figure below uses that cost.

  • m_ij: arm B’s multiplier for a gated tranche, taken from the journal’s applied_multiplier. Arm B orders x_ij = m_ij × b_ij.

    • When the order differs from the decision, m comes from the order: m_ij = x_ij / b_ij, the ordered stake over the budget. For a tranche B missed (no order filled inside its window), x_ij = 0, so m_ij = 0, although the journal still holds the model’s multiplier.
    • A gated cell with no dca_tranche row (e.g. an adjust_trade_position exception that escaped the hook, a defect under §5, DCA tranche gating) has m_ij = 0: nothing was bought and the budget carries. It is a B-missed tranche.
    • A skip gives m = 0, and a fallback gives m = 1.0, which deploys the whole budget b_ij, carried part included (§5, DCA tranche gating).
    • Otherwise m is in [0.25, 1.0].
    • Arm A always has m = 1.0, so its b_ij is s unless it missed the coin’s previous tranche.
  • Withheld amount w_ij = b_ij − x_ij = (1 − m_ij) × b_ij USDT, for a gated tranche. It is deferred to τ_j+1, where it joins b_i,j+1 and is decided on again.

    • A decision is withheld when w_ij > 0, so skips and shrinks both count.
    • In tranche-equivalents, ω_ij = w_ij / s, and W = Σ ω_ij. A carried amount that is withheld again counts again, because each week’s deferral is a separate decision. So ω can exceed 1.
  • Tranche return r_ij: the net return of arm A’s actual fill for tranche (i, j), held to the window end, per USDT of cost with entry fee. It is (units × P_i,end × (1 − f) − cost with entry fee) / (cost with entry fee).

    • P_i,end is coin i’s last hourly close at or before the window end, or its delisting price (§5, DCA mechanics).
    • Arm A is the counterfactual for every arm-B decision. No shadow ledger and no backtest are needed.
  • Deferral outcome ρ_ij = r_ij − r_i,j+1: per USDT, buying at τ_j (the skipped date) minus buying a week later at τ_j+1, both at arm A’s fills and held to the window end.

    • ρ_ij < 0 means the deferral paid: the coin was cheaper a week later.
    • If coin i has no tranche j + 1 (it was delisted, or τ_j+1 is after the window end), r_i,j+1 = 0, because the withheld amount stays in USDT at par.
    • Over several weeks. A USDT withheld at τ_j and finally bought at τ_j+n accrues ρ_ij + … + ρ_i,j+n−1 = r_ij − r_i,j+n. That is its return if bought at the skipped date minus its return as actually bought, split by week. Each decision is credited only for the week it controlled.
  • Matched: a non-fallback arm-B decision for a gated tranche (i, j), where arm A filled (i, j) and also (i, j + 1) when that tranche exists, and B did not miss (i, j). Any other non-fallback decision is unmatched.

    • A tranche B missed is unmatched, even when A filled it: a pure execution failure is not an LLM deferral. It is excluded from the Δ3-D cells and from n_m, W_m and D_m, counts towards the 20% unmatched limit (its w = b > 0), and still carries m = 0 in H3-D’s replay, because its budget did carry. A cell with no dca_tranche row counts the same way.
  • Counts, matched or all. Every DCA count says which set it is over:

    • matched (subscript m): n_m, the matched withheld decisions (w > 0); W_m = Σ ω_ij over them; D_m, the distinct tranche dates among them. ρ̄_A, ρ̄_W, Δ3-D, the hit rates, the H3-D test and the market and skill terms all use matched decisions only;
    • all: W_all = Σ ω_ij over every non-fallback withheld decision, matched or not, and the unmatched share, unmatched withheld decisions / all non-fallback withheld decisions. W_all is reported beside W_m, and B_vo-D uses all decisions.
  • Base rate: ρ̄_A is the equal-weight mean of ρ_ij over all matched decisions, withheld or not. The base hit rate is the share of those with ρ_ij < 0, i.e. weeks in which deferring would have paid.

  • Final value V: idle USDT plus holdings at liquidation value (hourly close × (1 − f)) at the window end. Arm B’s V is net of its LLM spend. R = (V − W0) / W0.

    • Idle USDT = W0 − Σ (cost with entry fee) over the arm’s fills, computed by the collector from the trades database. It is never read from freqtrade’s wallet: the 2026.9 dry-run wallet subtracts open trades’ stake without their entry fees [FREQTRADE-WALLETS-SOURCE-01].
  • Average cost per coin: quote spent including entry fees, divided by units bought, per coin and per arm.

  • Idle cash: USDT not held in coins. It is reported hourly, as a time-weighted mean, and at the window end.

  • Decomposition of D1 (an accounting identity, reported with every DCA verdict):

    D1 = R(B) − R(A) = market term + skill term + fill term − LLM cost term + residual

    Why it holds. If both arms fill every order at arm A’s prices with no truncation, a fill of ordered stake x_ij costs (1 + f) × x_ij and is worth (1 + f) × x_ij × (1 + r_ij) at the window end, so V = W0 + (1 + f) × Σ_ij x_ij × r_ij. Arm A orders s at every tranche, and B’s order is x_ij = s + w_i,j−1 − w_ij (with w_i,−1 = 0, and w_i,12 = 0 because the final tranche invests everything). The sums over j telescope to V(B) − V(A) = −(1 + f) × Σ w_ij × ρ_ij. The factor (1 + f) = 1.001 is there because x is the pre-fee stake.

    TermFormulaWhat it is
    Market term−(1 + f) × (s / W0) × W_m × ρ̄_AWhat deferring the matched W_m tranche-equivalents for a week at random would have cost or saved, given the market’s weekly moves
    Skill term−(1 + f) × (s / W0) × W_m × Δ3-DWhat choosing which weeks to defer added or lost, over matched decisions
    Fill term(1 / W0) × Σ c^B_ij × (r^B_ij − r_ij)Arm B’s fills against arm A’s fills for the same tranches (latency, order book, and B’s larger carried orders), where c^B_ij is B’s cost with entry fee and r^B_ij uses B’s fill
    LLM cost termB’s LLM spend / W0Near zero at USD 0.042 per million input tokens (§5, LLM provider and budget); still deducted
    ResidualD1 minus the four terms aboveReported, never hidden, with its computable parts listed separately: (i) unmatched withheld decisions, −(1 + f) × (1 / W0) × Σ_unmatched w_ij × ρ_ij where ρ is computable; (ii) tranches missed in one arm only; (iii) lot-step truncation residue in either arm; what is left is rounding

    Market term + skill term = −(1 + f) × (s / W0) × W_m × ρ̄_W = −(1 + f) × (1 / W0) × Σ_matched w_ij × ρ_ij, exactly, because both use W_m. The five terms sum to D1 exactly, because the residual is defined as the difference; with every decision matched, no missed tranche and no truncation, the residual is rounding only.

Hypotheses

  • H1-D (primary, return): D1 = R(B) − R(A), on final values net of LLM cost.
    • Pass: D1 ≥ +1.0 pp and the 90% CI lower bound > 0.
    • Fail: D1 ≤ −1.0 pp and the 90% CI upper bound < 0.
    • Otherwise undecided.
    • The CI and its bootstrap are H1’s (§6, Statistics). DCA equity has no external cash flows (a buy only converts USDT into coins inside the wallet), so daily equity returns are well defined.
    • What H1-D measures: timing. Both arms invest the same budget, 13 × s per coin. Arm B can only move part of a coin’s tranche to a later week, and the final tranche invests what remains. So D1 is the summed result of B’s one-week deferrals, −(1/W0) × Σ w_ij × ρ_ij, plus fills, LLM cost and the residual. It is not a test of abstention: withheld money is never left out of the market for the rest of the window.
  • H2-D (risk, non-inferiority): D2 = MDD(B) − MDD(A), on hourly equity, in pp.
    • Pass: D2 ≤ 2.0.
    • Fail: D2 > 2.0.
    • Arm B’s cumulative spend on a coin is never above arm A’s before τ_12, because B only defers, unless arm A missed a tranche that B filled (A’s budget then carries, so A’s spend falls behind; §5, Execution window). The two are equal once the final tranche is bought. Their unit counts differ: B holds more units than A when its deferred buys filled lower, and fewer when they filled higher. So H2-D fails when B’s deferrals concentrate exposure in coins that then fall, or leave it holding more units into a late fall. It is a guard against that, not a hurdle.
  • H3-D (deferral quality): compares the outcomes of withheld amounts with the outcomes of all decisions, over matched decisions.
    • Δ3-D = ρ̄_W − ρ̄_A, where ρ̄_W = Σ_matched ω_ij × ρ_ij / W_m is the withheld-weighted mean deferral outcome over matched decisions.
      • Δ3-D < 0 means the gate deferred in weeks when buying a week later paid more than in the average week.
      • Δ3-D is not centred on 0 for a deferral pattern unrelated to prices. E[Δ3-D] ≈ 0 only averaged over market paths. On one path, such a pattern still gives Δ3-D = Σ_j (π_j − 1/12) × ρ̄_j, where π_j is date j’s expected share of W_m and ρ̄_j is that date’s mean ρ, because carried amounts weight later weeks more. A constant “always defer” policy is a lump sum at τ_12, and its Δ3-D is a fixed function of the path. So H3-D does not compare Δ3-D with 0. It compares it with arm B’s own decisions placed on randomly chosen dates, on the same prices.
    • Withheld hit rate = Σ_matched ω_ij × [ρ_ij < 0] / W_m. It is reported beside the base hit rate as a diagnostic and is not a verdict input: its null rate under carry-forward is not controlled, and as a pass-only condition it made the rule asymmetric.
    • The test: a randomization test conditioned on the realised prices.
      • Eligible cells: the gated tranches (i, j), j = 0..11. The final tranche j = 12 is ungated and excluded: nothing is moved into or out of it, and it invests what remains in every draw.

      • One null draw. Take a uniformly random permutation π of the 12 gated dates and move arm B’s decisions with their dates: every coin i gets m*_ij = m_i,π(j), where m is B’s realised multiplier matrix (1.0 for a fallback, 0 for a tranche B missed or a cell with no row; a tranche that does not exist after a delisting stays empty, drops any multiplier moved onto it, and passes ×1.0 to the cell it is moved to). Replay the carry-forward recursion (§5, Carry-forward) on m*, and compute Δ*3-D over the same matched cells with every ρ_ij at its realised value. The matched cells and ρ̄_A are the same in every draw, so a draw differs from B only in which dates its decisions fell on.

      • What the draws hold fixed. Each draw keeps B’s number of withheld decisions, its multipliers, and what it did across the coins on each date. Withheld amounts and W_m change, because the carry-forward is replayed.

      • Why whole dates are permuted. All coins of a tranche date are decided in one batch with the same headlines, and the coins move together, so a gate’s errors and its skill both act mostly per date. A policy that does the same on every date (always defer, always approve, always skip the same coins) is unchanged by every permutation, so it gets p = 1 and can neither pass nor fail: it sits exactly at the null by construction. A policy whose decisions do not depend on the price path (it defers at random, per coin or per whole date) is exchangeable across dates, so its pass and fail rates are each at most α on every price path. That guarantee covers path-independent policies only. A fixed date pattern chosen in advance is not exchangeable, so that guarantee does not cover it; in the sketch, “defer the first six weeks” passed in 3.4% of runs at α = 0.05 and 0.2% at α = 0.005. The two alternatives considered fail one of these: at α = 0.05, permuting dates separately within each coin lets a date-coherent no-information policy pass in about 8–12% of runs and a fixed “defer the first six weeks” pattern in 25%, and permuting coins within each date lets “always skip the same 3 coins” pass in 14% (experiments/llm-gate-ab/power/results.md).

      • Path-reactive policies are not covered, and arm B is one. At τ_j the gate sees the week that just ended (its inputs are the portfolio and a market summary as of τ_j), and ρ_i,j−1 > 0 exactly when coin i rose over week j−1, because ρ_i,j−1 = c × P_i,end × (1/P_i,j−1 − 1/P_i,j) with c > 0. A draw that moves a decision onto an earlier date hands the null that move as look-ahead. So a rule that reacts to past prices without predicting anything is not at the null: a contrarian rule (“skip after a rally”) meets an anti-skilled null and passes too often, and its trend-following mirror fails too often. In a zero-drift random walk, where past prices predict nothing, “skip a coin while above its 28-day MA” passed H3-D in 27.8% of runs at α = 0.05 and its mirror failed in 30.2% (pack PR #42 statistical re-review). Hence α = 0.005 below, the pre-registered rule benchmarks, and the second finding “LLM beyond simple rules” (Verdict rule, DCA family). Disclosed: even at α = 0.005, a past-price rule with no predictive power passes or fails H3-D more often than α; the measured rates are in the no-information table under “Statistical power, stated honestly”.

      • H3-D credits date selection only. A gate whose skill is per coin and persistent (“defer coin X for the whole window, it is in decline”) does the same on every date, gets p = 1, and can never read “Agentic better”. If it is wrong, H1-D can still fail, so only “Agentic worse” is reachable for that kind of skill. The same holds for any rule keyed to the day of the week, because every τ_j falls on the same weekday at 12:00 UTC (a Monday, for T0 = 2026-10-26).

      • Draws and seed: 10,000 draws from a fresh random.Random(stats.SEED + 4) = random.Random(20261005), constructed at the H3-D call for each strategy verdict and fixed before any data. H3-D has its own stream: stats.SEED itself is the stationary bootstrap’s default (H1-D), SEED + 2 is H3’s cluster bootstrap, SEED + 3 the gain CI, SEED + 5 the “LLM beyond simple rules” bootstrap and SEED + 6 rule 6’s u.

      • The shuffle is written out, using only random(), whose stream is stable across Python versions. random.shuffle is not used: since Python 3.11 it takes no random argument and draws through _randbelow. Per draw:

        a = list(range(12))
        for i in range(11, 0, -1):
            k = int(rng.random() * (i + 1))
            a[i], a[k] = a[k], a[i]
        

        then m*_ij = m_i,a[j]: a[j] is the source date of the decision placed on date j. Draws are independent, with replacement, so the identity can recur; that is valid with the (1 + #) / (N + 1) form below.

      • Identity replay check. Before any p-value, the script replays the identity permutation (a[j] = j) through the carry-forward recursion and must reproduce every journaled w_ij within the lot-step tolerance (one lot step × fill price per order; §3, Budget). If it does not, the verdict script stops with an error and gives no verdict.

      • p-values, one-sided each way, ties against B. p_better = (1 + #{draws with Δ3-D ≤ Δ3-D}) / 10,001 and p_worse = (1 + #{draws with Δ3-D ≥ Δ3-D}) / 10,001, both compared after the verdict script’s rounding to 9 decimals. A tie counts in both, so it never helps B pass or fail.

        • An undefined Δ*3-D is a tie. If a draw moves every withheld amount onto unmatched or delisted cells, its W_m* = 0 and Δ*3-D is undefined (e.g. arm A missed a whole date, or a coin delisted early). Such a draw counts in both counts.
    • Pass: p_better ≤ α, i.e. B’s dates did better than at least 99.5% of random date placements of its own decisions.
    • Fail: p_worse ≤ α, i.e. they did worse than at least 99.5%.
    • α = 0.005 on each side, calibrated in the sketch so that the pre-registered past-price rules, which have no predictive power there, pass and fail at about 5% or below in a zero-drift market (at most 5.1% and 5.4%; 7.9% for the 28-day-MA rule at +100%/yr drift); at α = 0.05 they reached 24% and 29%. Path-independent policies stay at about α (at most 1.1%). The price is power: at a true Δ3-D of −7.5 pp, H3-D alone passes in 66.7% of runs instead of 81.8% (power table below). The two cannot both hold. The dashboard shows Δ3-D, the null draws’ median and 5th–95th percentiles, and both p-values.
    • Not evaluable if any of these holds (the minimum skip count):
      • n_m < 20 matched withheld decisions (w > 0);
      • W_m < 10 matched tranche-equivalents;
      • D_m < 4, i.e. the matched withheld decisions fall on fewer than 4 distinct tranche dates;
      • unmatched withheld decisions exceed 20% of all non-fallback withheld decisions.
    • Otherwise undecided.
    • Why these minimums.
      • 20 is H3’s existing minimum.
      • W_m ≥ 10 stops token shrinks from counting as skips. ×0.95 on every gated tranche gives 120 withheld decisions but, carry-forward included, W = 6.3.
      • With withheld decisions on 1 to 3 dates, the result rests on 1 to 3 weekly market moves that every coin shares, and one date gives the date permutation only 12 distinct arrangements. Hence 4 dates.
      • The 20% unmatched share is H3’s.

Verdict rule (DCA family, applied in order)

  1. Void: any void condition below holds.
  2. Unavailable (at the readout only; erratum 2026-10-08, §14): no void condition holds, but an arm’s restart and state-change ledger cannot be shown complete over [T0, T_end] (§5, Market and timing). Without it a manual intervention or restart downtime may be missing, so no other verdict is published. The run stays in the public history with its data and the reason.
  3. Agentic better: H1-D, H2-D and H3-D all pass.
  4. Agentic worse: H1-D fails, H2-D fails, or H3-D fails.
  5. Inconclusive: everything else, with its reason, e.g. “H3-D not evaluable (7 withheld decisions, 20 needed)”.

Every DCA reason string ends with the D1 decomposition, e.g. “D1 −0.60 pp = market −0.45 pp + skill −0.10 pp + fills −0.05 pp − LLM 0.00 pp + residual 0.00 pp”.

Second finding: “LLM beyond simple rules” (separately labelled; it never replaces the verdict).

  • What it asks. H3-D compares B with its own decisions on random dates. A rule that reacts to past prices without predicting them can still pass that test (H3-D above), so the readout also asks whether the LLM did better than a one-line rule anyone could have run on the same prices.

  • One primary benchmark, fixed now: rule 1 of the table below, “skip coin i at τ_j while P_i(τ_j) is above its 28-day MA”. Not its mirror, and no other rule.

  • Outcomes, from D1(B) − D1(rule 1) and its one-sided bootstrap bounds:

    • yes (“LLM beyond simple rules”) only if both: the verdict is “Agentic better”; and the lower bound of D1(B) − D1(rule 1) at one-sided α = 0.05 (the 5th percentile) is above 0;
    • rule better (“simple rule beats LLM”) if the upper bound of D1(B) − D1(rule 1) at one-sided α = 0.01 (the 99th percentile) is below 0, whatever the verdict, except Void or Unavailable (errata below);
    • no otherwise. The two named outcomes cannot both hold.
    • Not reported (null) on a Void or Unavailable run (erratum, 2026-10-08, §14), and when rule 1 cannot be replayed because a cell lacks the price history it reads (erratum, 2026-10-07, §14). A void run supports no claim. Without rule 1’s replay nothing was compared, so “no” would claim a result that was never computed. The dashboard shows the reason instead of an outcome.
  • One-sided α = 0.05 for yes and 0.01 for rule better, each calibrated by simulation. For each side the level is the largest of 0.05, 0.025, 0.01 and 0.005 whose worst-case rate over every no-information policy in the power sketch (the path-independent ones and the 12 past-price rules, at zero drift, +100%/yr drift and in the vol-regime market) is at most 5%:

    One-sided α0.050.0250.010.005
    yes (“Agentic better” and lower bound > 0), worst no-information rate0.3%0.1%0.1%0.1%
    rule better (upper bound < 0), worst no-information rate11.1%7.0%3.4%2.5%

    The levels differ because the measured error rates differ, not for convenience. The bootstrap’s bounds are too narrow under no information: it resamples 90 daily returns, but D1(B) − D1(rule 1) is driven by which of 12 weekly dates each side deferred, and at the 5-day mean block the bootstrap’s spread of it is below its spread across runs for every no-information policy, by up to about 40% (block_check.md beside the sketch). A longer or weekly-matched mean block makes the bootstrap’s spread smaller, not larger, and no mean block from 1 to 14 days brings “rule better” at α = 0.05 down to 5% (worst cases 10.2–19.5% in that check), so the bootstrap is kept as H1-D’s and the level is tightened instead. “yes” needs “Agentic better” too, which includes H3-D at 0.005, so it stays far below 5% at H1-D’s level of 0.05; “rule better” reads the bound alone, so it needs 0.01. There is one comparison, so no multiplicity correction.

  • Bounds. Paired stationary block bootstrap (mean block 5 days) of daily equity returns of B and of rule 1’s replay, as for H1-D: 10,000 resamples from a fresh random.Random(stats.SEED + 5). The bounds are percentiles of (1 + return of B) − (1 + return of rule 1) compounded over the window, as H1-D’s interval is for B − A: the 5th for yes, the 99th for rule better.

  • Why rule 1, chosen a priori. “Don’t buy while the price is stretched above its moving average” is the classic buy-the-dip timing rule for a DCA plan, the rule a reader is most likely to run instead of an LLM gate. It reads only what the gate sees at τ_j: each coin’s own past prices. And it is the strongest no-skill impostor in the sketch: it passes H3-D in more zero-drift runs than any other rule (5.1% at α = 0.005; 7.9% at +100%/yr drift), so “the LLM did no better than this” is the comparison H3-D most needs beside it. The choice and its direction were fixed from these reasons, before any experiment data; its mirror is a trend-following rule that no buy-the-dip reader would substitute for a deferral gate.

  • Why not “beat all 12” (rejected). The previous draft required B’s D1 to beat every one of the 12 rules (an intersection-union test). Beating all of them is beating the best of them, chosen after the fact, and each rule’s mirror has a D1 of roughly the opposite sign, so in most windows one of each pair is well ahead even though no rule has any skill: in the sketch the best of the 12 averaged about 3.9 pp of D1, twice a skilled gate’s 2 pp. That finding reached 0–0.9% of runs across the skilled gates in the sketch (1.7% at most with the mirrors dropped), so a “no” carried no information. One benchmark fixed in advance has no selection and no mirror pair, and its rates are in the power table below.

  • The other 11 rules (rule 1’s mirror, and rules 2–6 with their mirrors) stay pre-registered, frozen and replayed, and are reported descriptively only: no CI, p-value, claim or label is attached to them, and the readout never names a “best rule”.

  • Headline. The headline line stays the primary verdict. The second finding is shown beside it, labelled “LLM beyond simple rules (vs. skip above the 28-day MA): yes / no / rule better”, with the table below.

Pre-registered rule benchmarks (DCA). Twelve rules, six and their mirrors. Rule 1 (not its mirror) is the primary benchmark of the second finding; the other 11 are descriptive. Each rule decides skip (m = 0) or approve (m = 1.0) for every gated tranche (i, j), j = 0..11, from what a bot could see at τ_j. Prices are hourly closes: P_i(t) is coin i’s last hourly close at or before t, and “d days before τ_j” means τ_j − 24d h.

#Rule: skip coin i at τ_j whenMirror: skip when
1P_i(τ_j) is above its 28-day MA, the mean of P_i(τ_j − d days) for d = 0..27 (“overextended”)below its 28-day MA
2j ≥ 1 and P_i(τ_j) is above arm A’s average cost of coin i over its fills before τ_j (“don’t average up”; §6, average cost per coin); j = 0 is approvedbelow that average cost (j = 0 approved)
3P_i(τ_j) > P_i(τ_j − 7 days): coin i rose over the past weekP_i(τ_j) < P_i(τ_j − 7 days)
4every coin, when the basket’s mean 7-day price ratio, mean_i P_i(τ_j) / P_i(τ_j − 7 days), is below 1 (“basket-down date”)that ratio is above 1
5every coin, when σ7 > σ28, where σn is the mean over basket coins of the SD of daily log returns over the n days ending at τ_j (“vol regime”)σ7 < σ28
6u_ij < 0.6 × j / 11 (“skip more near T_end”)u_ij < 0.6 × (11 − j) / 11
  • Rule 6’s u is a fixed matrix: u_ij is the (k × j + i + 1)-th random() of a fresh random.Random(stats.SEED + 6), i.e. drawn in date order j = 0..11 and, within a date, in the basket’s rank order i = 0..k − 1. It is a no-information pattern that the DCA prompt makes plausible (the model is told about the final tranche), and it is not exchangeable across dates.
  • Replay. Each rule is replayed deterministically on the realised prices with the carry-forward recursion of §5 (identical to arm B’s: b_0 = s, x = m × b, b_j+1 = s + b − x, the final tranche invests what remains), filling every ordered stake at arm A’s fill price for that tranche, with arm A’s fee, no truncation and no LLM cost. A tranche arm A missed, or a pair arm A flagged as delisted, is treated as arm A treated it. The replay is a ledger, never an order.
  • Reported per rule, beside B, as point estimates: Δ3-D over the cells where arm A filled (i, j) and its next tranche, D1(R) = R(replay) − R(A), and D1(B) − D1(R). Only rule 1’s D1(B) − D1(R) carries bootstrap bounds, the second finding’s. No rule gets H3-D p-values or any other test. B’s own Δ3-D, p-values and D1 head the table.
  • Frozen before T0. The 12 rules, their parameters (28 days, 7 days, σ7 and σ28, 0.6, the u seed) and their code are frozen with the verdict script: the benchmark module is in FROZEN_FILES, its SHA-256 is recorded with the verdict-script SHA at T0, and a change after T0 voids the run (§3, condition 5). No rule is added, dropped or tuned after T0.
  • The rules are benchmarks, not strategies: nothing trades on them, and none of them is claimed to work.

Why there is no selection-effect condition.

  • In the signal family that condition stops “shrink everything in a falling market” from reading as skill.
  • In the DCA family a shrink and a skip are the same act: deferring part of the coin’s budget by a week. The rule that stops “defer whenever the market falls” from passing is H3-D’s randomization test. A deferral pattern independent of the price path passes H3-D only at the test’s false-pass rate (at most α = 0.5%), and a constant one never, by construction. A rule that reacts to past prices without predicting them can pass more often (the no-information table below); the “LLM beyond simple rules” finding shows whether B did better than the primary such rule, and the other pre-registered rules are shown beside it.
  • For DCA, B_vo-D replays arm B’s decisions with every shrink replaced by ×1.0, keeping the skips and recomputing the carry-forward, valued at arm A’s fills. It is reported, with skips-only and shrinks-only shares of D1, as a diagnostic, not as a verdict input.

Symmetry, and what market direction still does.

  • The rule is symmetric in the same way as the signal family’s: each hypothesis has its own fail condition, and any single fail reads as “Agentic worse”.
  • Market direction enters D1 only through the market term, −(1 + f) × (s / W0) × W_m × ρ̄_A, which is the drift over the weeks money waited. A steady rise makes it negative and a steady fall makes it positive, but each deferred amount misses only a week of drift per decision and ends up invested. Under the earlier no-carry-forward draft, withheld cash missed the rest of the window.
  • Withholding therefore does not lose by construction in a rising market. A gate that defers ahead of weekly dips gains in a rising market, and one that defers ahead of weekly rises loses in a falling one. H1-D measures that timing, and H3-D asks whether it beat the same decisions on random dates.
  • The decomposition shows how much of D1 came from the market’s weekly drift rather than from choosing which weeks to defer.

Statistical power, stated honestly.

These figures come from the pack’s own Monte Carlo sketch, not from a source: experiments/llm-gate-ab/power/power_sim.py, fixed seed 20261007, numpy pinned in requirements.txt, output committed as results.md beside it. Its assumptions:

  • the basket’s coins have daily log returns with 70% annualised volatility, pairwise correlation 0.75 and zero drift, with a 28-day price history before T0 for the rules; so past prices predict nothing, unless a row says otherwise;
  • there are 13 weekly tranches, each held to day 90, with carry-forward and the final tranche investing what remains; s = 75.38, W0 = 10,000, fee 0.001 on top of the stake, and B fills at A’s prices (erratum 2026-10-08, §14: it was s = 37.69 and W0 = 5,000; the sketch depends on s and W0 only through s / W0, which is 0.007538 in both, so no figure below changes);
  • k = 10, so 120 gated decisions, all matched;
  • each row is 1,000 simulated runs, with 2,000 permutation draws for H3-D and 2,000 stationary-bootstrap resamples for H1-D and the rule comparison (the verdict script uses 10,000). The Monte Carlo SE of a rate is about 0.7 points near 5% and 0.3 near 1%.

No information, α = 0.005 per side (α = 0.05 shown for comparison). Shares of all runs. “Beyond rules (primary)” and “Rule better” are the second finding’s two named outcomes against rule 1, at their calibrated one-sided levels, α = 0.05 and α = 0.01.

PolicyNot evaluableH3-D pass / failH3-D pass / fail at α = 0.05H1-D passAgentic betterAgentic better at α = 0.05Beyond rules (primary)Rule better
Path-independent
Always defer (constant)0%0% / 0%0% / 0%5.2%0%0%0%3.4%
Random skipper, 20% / 50% / 80% of tranches15.1% / 0% / 0%0.2–0.6% / 0.2–0.7%3.7–6.0% / 4.1–5.5%2.6–7.2%0.2–0.3%0.3–1.3%0.1%1.9–2.4%
Random shrinker, 40% of tranches at ×0 to ×0.750%0.2% / 0.6%5.7% / 4.5%4.5%0.1%0.7%0%3.0%
Whole dates skipped at random, 25% of dates66.3%0.2% / 0%1.5% / 2.1%5.6%0.1%0.7%0.1%2.0%
“Headline mood”: a quarter of dates fearful17.2%0.3% / 0.4%3.6% / 4.3%4.4%0.2%1.5%0%2.7%
Fixed: defer the first six weeks0%0.2% / 0.5%3.4% / 5.0%6.1%0.1%1.8%0.1%2.5%
Fixed: always skip the same 3 coins0%0% / 0%0% / 0%6.5%0%0%0%2.4%
Random ramp to T_end, skip p = 0.6 j / 11 (zero / +100%/yr drift)0%1.0–1.1% / 0.2%7.5–7.8% / 4.0–4.6%7.2% / 1.6%0.2–0.3%1.0–2.9%0%2.3% / 1.6%
Past-price rules (the 12 benchmarks)
1. Skip while above the 28-day MA3.4%5.1% / 0%24.3% / 0.2%11.0%1.5%5.7%0% (by construction)0% (by construction)
1 mirror. Skip while below it2.2%0.1% / 4.6%0.6% / 23.5%3.8%0.1%0.6%0.1%1.9%
2. Don’t average up12.0%4.2% / 0%23.6% / 0%11.5%1.7%5.9%0%1.6%
2 mirror. Don’t average down14.1%0.1% / 5.4%0.6% / 28.7%3.1%0.1%0.3%0.1%3.1%
3. Skip after an up week0%2.5% / 0%12.4% / 0%10.1%0.5%2.7%0%2.0%
3 mirror. Skip after a down week0%0% / 2.9%0.2% / 14.4%2.5%0%0%0%3.2%
4. Basket-down date6.0%0% / 1.6%0.4% / 10.5%2.9%0%0.4%0%2.7%
4 mirror. Basket-up date7.0%1.1% / 0%9.9% / 0.2%8.3%0.6%3.3%0.1%1.7%
5. Vol regime, σ7 > σ2842.1%0.3% / 0.2%3.5% / 1.8%7.7%0.3%1.9%0%2.8%
5 mirror. σ7 < σ280%0.5% / 0.3%3.8% / 4.4%6.4%0.4%1.4%0.3%1.8%
6. Skip more near T_end (frozen u)0%1.9% / 0.5%10.2% / 5.7%5.6%1.1%3.1%0.1%1.9%
6 mirror. Skip more near T00%1.0% / 0.9%5.8% / 9.1%5.6%0.6%2.2%0.1%2.9%
1 at +100%/yr drift0.4%7.9% / 0%32.3% / 0%4.1%0.5%2.4%0% (by construction)0% (by construction)
5 in a two-state vol-regime market24.1%0.3% / 0.6%3.9% / 4.1%6.3%0.3%2.2%0%1.5%
  • Path-independent policies stay at about α: H3-D pass and fail at most 0.7% when exchangeable across dates, and 1.1% for the non-exchangeable random ramp toward T_end, which passed 7.5–7.8% at α = 0.05 (the re-review measured 7.2–8.3%). They read “Agentic better” in at most 0.3% of runs.
  • Past-price rules with no predictive power pass H3-D in up to 5.1% of runs (skip above the 28-day MA) and fail in up to 5.4% (the mirror of “don’t average up”); at +100%/yr drift the 28-day-MA rule passes in 7.9%. At α = 0.05 the same rules passed in up to 24% and failed in up to 29%. Even at α = 0.005 they read “Agentic better” in up to 1.7% of runs (0.5% at +100%/yr drift), against 5.7–5.9% at α = 0.05, because H1-D passes in 10–12% of runs for the contrarian rules. This is the disclosed residual: a reactive gate with no skill can still reach the primary “Agentic better” in about 1–2% of runs, which is why the rule benchmarks are shown beside B.
  • The second finding under no information. “LLM beyond simple rules” reads yes in at most 0.3% of runs for every no-information policy, because it needs “Agentic better” first. When B is rule 1 itself, D1(B) − D1(rule 1) is exactly 0 in every resample, so neither named outcome can hold: 0% by construction, with or without drift. “Rule better” at its calibrated α = 0.01 reads in 1.5–3.4% of runs for every other no-information policy (worst: always defer, 3.4%). At α = 0.05 it would have read in 4.0–11.1%, which is why its level is tightened (calibration table above).
  • The alternative nulls, at α = 0.05 (results.md): permuting dates separately within each coin lets a date-coherent no-information policy pass in about 8–12% of runs, a fixed “defer the first six weeks” pattern in 25%, and the 28-day-MA rule in 54%; permuting coins within each date lets “always skip the same 3 coins” pass in 14%.

Skilled gate: the joint probability of “Agentic better” at α = 0.005. The gate skips Bin(120, 0.2) tranches per run, chosen by a noisy score of the true deferral outcome ρ. The true edge is the mean Δ3-D over evaluable runs. Every share is of all runs, with not-evaluable runs counted as not passing. “Rule 1 D1” is the mean D1 of the primary benchmark; “Best rule D1” is the mean over runs of the highest D1 among the 12 rules. “All 12 (rejected)” is the withdrawn design, shown for reference only.

True edge (Δ3-D)Mean D1Rule 1 D1Best rule D1Not evaluableH3-D pass (at α = 0.05)H1-D passH2-D passAgentic better (at α = 0.05)Agentic worseBeyond rules (primary)Rule betterAll 12 (rejected)
−2.1 pp0.38 pp−0.48 pp3.72 pp14.2%5.1% (23.8%)11.1%100%2.8% (6.9%)1.3%0.2%1.1%0%
−4.3 pp0.87 pp−0.58 pp3.98 pp14.1%23.2% (55.0%)31.2%100%13.2% (23.9%)0.4%1.2%0.6%0%
−6.1 pp1.23 pp−0.64 pp3.83 pp15.9%47.6% (74.6%)53.8%100%32.9% (45.1%)0%3.5%0.3%0.1%
−7.5 pp1.57 pp−0.67 pp3.88 pp15.5%66.7% (81.8%)70.8%100%51.5% (59.6%)0%6.9%0.2%0.4%
−9.3 pp1.95 pp−0.46 pp3.86 pp14.5%81.2% (85.1%)87.8%100%73.8% (76.5%)0%8.5%0.1%0.9%
  • What a skilled gate needs: a true Δ3-D of about −9 pp to reach “Agentic better” in about three runs out of four, and about −7.5 pp for an even chance; one decision’s ρ has an SD of about 9.8 pp. The move to α = 0.005 costs H3-D power (81.8% → 66.7% at −7.5 pp) but costs the joint verdict less (59.6% → 51.5%), because H1-D binds anyway.
  • H1-D is the binding test. D1’s skill part is about (1 + f) × (s / W0) × W_m × |Δ3-D| ≈ 0.0075 × 30 × 0.08 ≈ 1.8 pp at a 20% skip rate and −8 pp, against H1-D’s +1.0 pp bar and its bootstrap interval. H2-D passed in every run, because deferring lowers B’s drawdown.
  • “LLM beyond simple rules” against the primary rule is reachable but rare in 90 days: 0.2% / 1.2% / 3.5% / 6.9% / 8.5% of runs at a true Δ3-D of −2.1 / −4.3 / −6.1 / −7.5 / −9.3 pp, against 0–0.9% for the rejected all-12 design. Rule 1 has no skill (its D1 averages −0.5 to −0.7 pp here), but it skips about half the tranches on a different pattern from B’s, so D1(B) − D1(rule 1) has a wide interval. A “no” is still the expected outcome and is not evidence against the LLM; a “yes” would be strong evidence. A skilled gate reads “rule better” at α = 0.01 in at most 1.1% of runs, falling as skill rises.
  • Not evaluable. At a 20% skip rate the 20-decision minimum alone makes P(Bin(120, 0.2) ≤ 19) = 15.2% of runs not evaluable (exact binomial), so no gate at that rate reaches “Agentic better” in more than about 85% of runs.

The effective sample is smaller than the decision count, because coins move together and every decision of a tranche date shares that week’s market move. “Inconclusive” is the expected DCA verdict.

Void conditions (per strategy unless stated)

A void run is still published with its data and the reason.

  1. The budget cap is hit before day 75 (§5, LLM provider).
  2. The fallback rate exceeds 10% of B’s gate calls, or cumulative fallback time exceeds 24h. Cumulative means repeated shorter outages add up.
    • Fallback time, exact definition. Order B’s journal rows by timestamp and leave out budget_exhausted rows (condition 1 covers them). For each run of consecutive fallback rows, count the time from the run’s first row to the next non-fallback row, or to the end of the window (or the evaluation time, if earlier) when none follows. Sum over runs. The gate sees the provider only when a signal arrives, so a quiet spell after a fallback counts in full. This is the conservative reading: at low signal rates one fallback followed by a long quiet spell can void the run.
    • DCA family (amendment 1): only the rate clause applies, i.e. more than 10% of B’s tranche decisions are fallbacks. The time clause does not apply, for two reasons.
      • The DCA gate sees the provider only at the weekly tranche times, so every gap between decisions is a week by design, and one failed batch would count about 7 days and void the run.
      • A DCA fallback deploys the coin’s whole budget, carried part included (§5, DCA tranche gating). That brings B’s cumulative spend on the coin back to arm A’s and adds no deferral of its own (w = 0), so an outage can only remove LLM decisions from D1 and H3-D, never add a difference.
    • DCA retries before a fallback counts. A tranche whose call failed for a transient cause is retried inside its 24 h execution window (DCA tranche gating), and only a tranche still without a valid decision at its last attempt is a fallback. All 10 coins of a tranche date are called within minutes, so without retries one provider outage, or one hostile headline that triggers the 403 guardrail, would give 10 fallbacks (8.3% of 120), and two such Mondays would void the run.
  3. Any arm’s cumulative downtime exceeds 24h, or the downtime difference between the strategy’s arms exceeds 6h.
  4. Any manual intervention in any arm: force entry or exit, config or database edit, or a restart outside the restart policy.
  5. Any pinned item changes after T0: model, served provider, served model, prompt hash, temperature, request config hash, strategy commit, config hash, whitelist, or verdict-script SHA.
    • The request config hash covers reasoning effort, max_tokens, response_format, provider routing, the timeout, and the hashes of the injection-pattern list and the output schema.
    • Amendment 1: each strategy’s B arm has its own prompt template (the DCA prompt differs from TrendCash’s), so the prompt hash and request config hash are pinned per strategy. DCA’s basket size k and stake s sit in its config, and the config hash covers them.
  6. The journal fails validation: a schema error or a duplicate decision for the same (strategy, pair, signal candle, side). A journal write failure also voids the run: the gate logs it to <journal>.errors.jsonl, so a non-empty error log means a decision has no row. So does an exit-tag declaration violation: a non-empty <journal>.violations.jsonl means arm B emitted an exit tag or custom_exit string it did not declare (§5, Exit gating), so its gating cannot be trusted.
    • For DCA the key is (strategy, pair, τ_j, dca_tranche).
  7. The final data is still incomplete at T_end + 7 days (§14, 2026-10-08, pack issue #59). Void, with the reason “readout data incomplete” and the list of what was missing; the Void readout is frozen like any other.
    • Recorded data only. Readiness counts data by the time the collector recorded it, never by when the metrics happen to be refreshed: a poll’s timestamp, the shadow ledger’s built_at, a price’s first fetch time and a journal row’s first ingest time. Data recorded after T_end + 7 days never counts toward readiness. Data recorded before it always counts, however late the first refresh runs.
    • Delisted-pair exception, exactly. A pair’s missing final-hour price is excepted when the pair is delisted by the test of §5 (DCA mechanics, Delisting), on data recorded by then: (a) an arm’s delisting flag, recorded at τ_j; (b) no closed hourly candle for 48 h: the pair’s last recorded price is more than 48 h before the latest recorded price of the strategy’s pairs (the baselines’ rule); or (c) no longer an active market: the pair is missing from every arm’s whitelist at its first ok poll at or after T_end (freqtrade drops a market the exchange no longer lists as active). Rule (c) covers a delisting in the last 48 h of the window, which (a) and (b) cannot see. Any other missing final-hour price is not excepted.
    • Reason. The run’s own void reasons come first, then, for a run that was Unavailable, its restart-detection reasons, then “readout data incomplete: still missing at <T_end + 7 days> (T_end + 7 days): ”. A DCA reason ends with the D1 decomposition, as every DCA reason does. Void outranks unavailable (Verdict rule), so an Unavailable run whose data is still missing becomes Void, and the headline counts it instead of reading “Verdict unavailable”.
  8. Any arm trade opened, or any arm-B journal row written, before T0: Void with the reason “trading before T0” and the items (§14, 2026-10-09, run-start gate). Exactly T0 is allowed.

4. Why forward paper trading, not backtesting

The LLM arm cannot be backtested honestly. Model training data overlaps historical prices and news, so a backtested LLM is graded on an exam it may have memorised. Only forward, live-data paper trading gives the LLM arm a fair test. The deterministic arms run forward too, so both arms see identical conditions.

The deterministic strategy has no such problem. After skill 04 confirms it has no lookahead bias, a post-hoc backtest over the forward window reproduces the signals arm A saw live. That is why the shadow ledger (§6) may use one.

DCA needs neither: arm A’s live fills are the counterfactual for each arm-B tranche decision (§3, DCA family).

5. Experiment design

Arms (per strategy)

ArmEntryExit
A: Deterministic (signal family)Strategy signal is executed as-isEvery exit the strategy produces: exit signal or tag, custom exit, ROI, stoploss, trailing stop
B: LLM-gated (signal family)The signal goes to the decision model, which answers approve (the strategy’s own size, size_multiplier 1.0) or skip (journaled as reject). Shrinking is not offered since the 2026-10-21 amendment (§14)Gated exits go to the model (see Exit gating): approve lets the exit happen, skip keeps the position. ROI, stoploss, trailing stop, liquidation, emergency and force exits are never gated.
A: Deterministic (DCA family)Each scheduled tranche is bought as-is: s USDT of each basket coin at each τ_jNone before T_end. Holdings are marked at liquidation value.
B: LLM-gated (DCA family)Each coin’s gated tranche (j = 0..11) goes to the decision model, which approves it (×1.0) or skips it (×0; no shrink since the 2026-10-21 amendment, §14). The multiplier applies to the coin’s budget b_ij, i.e. s plus any carried amount. Unspent budget carries to the coin’s next tranche, and the final tranche (j = 12) invests everything remaining, with no LLM callNone before T_end
Baseline: HoldEqual-weight buy of the strategy’s pairs at T0, held to T_endNo bot; computed from prices
Baseline: DCA13 weekly buys of W0/13 USDT, equal-weight across the strategy’s pairs, from T0 (for DCA itself: the strategy’s own k and s, below)No bot; computed from prices

Baseline mechanics:

  • Hold and DCA pay the same fee and slippage model as A and B. Undeployed USDT counts at par.
  • Baselines use each strategy’s own pairs. A BTC/ETH strategy is compared with Hold and DCA of BTC/ETH, not with the full whitelist.
  • On a delisting, the baselines sell at the last available price, with fees, and hold USDT.
  • DCA strategy (amendment 1). Its DCA baseline uses the strategy’s own k pairs and stake s, so it is arm A’s plan computed from prices. It therefore doubles as a mechanical cross-check: a gap above 0.5 pp between arm A’s and the baseline’s R at the window end is flagged as a fault, as the shadow-ledger sanity check is.
    • Hold beside DCA invests what arm A invests, 13 × k × s (9,799.40 USDT at k = 10; erratum 2026-10-08, §14, it was 4,899.70), at T0 in equal weights, not W0. The other W0 − 13ks stays at par, so Hold, the DCA baseline and both arms deploy the same total.
    • Fee convention for both baselines of the DCA strategy: freqtrade’s. A buy of stake x gets x / (price × (1 + slippage)) units and costs x × (1 + fee), the fee on top, as in the arms (§3, Fee convention). baseline_equity has a stake-aware pre-fee mode (stake=, pack PR #49) that the DCA baselines use, so the 0.5 pp fault flag does not absorb a convention difference.

Hard rules for arm B

  • Gate only. The LLM never creates a trade without a strategy signal and never force-buys or force-sells. It can only shrink a position (multiplier ≤ 1.0), never enlarge it.

    • For DCA the multiplier applies to the coin’s budget b_ij, so an approval of a carried budget orders more than that week’s s (up to 13 × s), but it never takes B’s cumulative spend on the coin above arm A’s, except where arm A missed a tranche (§5, DCA tranche gating).
  • Fail-closed entries, fail-open exits. Any of these is a fallback:

    • timeout (20s total, retries included);
    • invalid output;
    • provider error (HTTP 4xx/5xx, rate limit);
    • a provider prompt-injection block (HTTP 403, journaled as prompt_injection_blocked) [OPENROUTER-GUARDRAILS-01];
    • budget exhausted;
    • a served model other than the pinned one (served_model_mismatch);
    • a gate error (gate_error: the inputs could not be built or a hook failed).

    On a fallback the entry is rejected and the exit is approved, and the cause is journaled. A gate_error row is journaled and cached like any other decision, so it counts against the void thresholds instead of hiding a broken input.

    • The 20s is a hard wall-clock deadline that includes DNS, connect and reading the body.
    • 429 and 5xx responses are retried inside the 20s budget, honouring Retry-After [OPENROUTER-LIMITS-01]. An invalid output is never retried, because that would be a re-ask.
    • A response with a size_multiplier outside [0.25, 1.0], or a multiplier on a reject, is invalid_output. It is never clamped.
    • DCA tranches have their own fallback rule; see DCA tranche gating below.
  • One decision per signal; re-asks are forbidden. freqtrade re-evaluates a denied entry or exit on later loops while the signal holds [FREQTRADE-BOT-SOURCE-01].

    • Exactly one LLM call is made per (strategy, pair, signal candle, side). The decision is cached, and every retry replays it without a call.
    • Only the first call is journaled. The validator rejects duplicate keys.
  • Hook order. freqtrade calls custom_stake_amount before confirm_trade_entry [FREQTRADE-BOT-SOURCE-01].

    • B’s custom_stake_amount makes, or replays, the decision. On approve it returns super().custom_stake_amount(...) * size_multiplier, so the strategy’s own sizing (e.g. TrendCash volatility sizing) is kept.
    • confirm_trade_entry only returns the decision cached for the current signal candle. A decision for an earlier candle never approves a later one; with none, the entry is rejected and journaled as gate_error.
    • An exception from the strategy’s own custom_stake_amount or confirm_trade_entry gets freqtrade’s default (the proposed stake, True), exactly as in arm A, and the gate still decides.
    • Unit test: for the same inputs, A’s stake equals B’s stake at multiplier 1.0.
  • Exit gating. freqtrade passes exit_reason = exit_tag or exit_check.exit_reason to confirm_trade_exit [FREQTRADE-BOT-SOURCE-01]. So signal exits arrive as their exit tag, and custom_exit exits as their string.

    • B gates every exit whose exit_reason is not one of roi, stop_loss, stoploss_on_exchange, trailing_stop_loss, liquidation, emergency_exit or force_exit. The gate-decision schema rejects gated rows with these reasons.
    • Strategies must never emit an exit tag or custom_exit reason equal to any freqtrade ExitType value. Arm B’s startup refuses to run unless the strategy declares exactly one of llm_gate_exit_tags (every exit tag and custom_exit string it emits) or llm_gate_exit_tags_none = True, and refuses a declared tag equal to an ExitType value.
    • No hook raises at runtime, because freqtrade swallows hook errors and would silently drop arm B’s signals or exits [FREQTRADE-INTERFACE-SOURCE-01]. An exit tag or custom_exit string the strategy did not declare is appended to <journal>.violations.jsonl. When such an exit reaches confirm_trade_exit it is approved (fail-open on exits, as above) and journaled as gate_error with the violation in rationale. The journal report computes every journal-side void reason per strategy, never pooled.
  • Pinned behaviour. These are fixed at T0 and journaled on every row:

    • model ID, and the served model from the response’s model field [OPENROUTER-API-OVERVIEW-01];
    • served provider: OpenRouter provider.order with exactly one slug and allow_fallbacks: false [OPENROUTER-PROVIDER-ROUTING-01]. The journal records the response’s provider field, never the requested value; a response without it is invalid_output;
    • prompt hash;
    • temperature 0;
    • request config hash (request_config_sha256): the request body without messages, so model, temperature, max_tokens, response_format, provider routing and reasoning effort, plus the timeout and the hashes of the prompt, the injection-pattern list and the output schema.

    Reasoning effort must be set explicitly at startup (a literal provider-default is allowed, a silent default is not), because the model’s default reasoning can use the whole max_tokens budget [OPENROUTER-REASONING-01]. Reasoning effort, max_tokens and the request config hash go in the run metadata. Changing any pinned item voids the run.

  • Recorded decisions. Every decision is journaled to schemas/gate-decision.schema.json. A row holds:

    • signal candle, trade id and proposed rate;
    • inputs digest, raw output, parsed decision, rationale and fallback cause;
    • the model’s size_multiplier and the applied_multiplier freqtrade actually used;
    • latency, tokens, cost, served provider, served model and request config hash.
  • Decision backend (amendment 2026-10-21, pack issue #77). Arm B runs with LLM_GATE_BACKEND=typesafe (§5, LLM provider and budget). The rules above apply, with these differences [TYPESAFE-SYSTEMONE-01]:

    • The request is POST /v1/systemone with the model, a state built from the same inputs the prompt carried (the side, the bot’s inputs and the screened headlines) and one choice question. Its instructions and its approve and skip criteria are pinned in strategies/llm_gate/prompts/gate-jev-v1.json (TrendCash) and dca-jev-v1.json (DCA) and restate the prompts’ rules. No OpenRouter-only field (provider, reasoning, response_format) is sent; temperature, max_tokens and reasoning effort do not apply.
    • approve is an approval at multiplier 1.0 (null on an exit) and skip is the schema’s reject. An answer that is not exactly one of the two, with a confidence and both probabilities in [0, 1], is invalid_output.
    • The row’s raw_output holds the answer (choice, confidence and both probabilities), and rationale is null, because the model returns no text. provider is the constant typesafe, since the response names none. served_model is the response’s model; anything other than jev-1.13.0 is served_model_mismatch. Tokens come from usage, and cost_usd is input tokens × USD 0.042 / 10^6.
    • prompt_sha256 is the question file’s hash. request_config_sha256 covers the request body without state (model and question), the backend, the timeout, the question hash, the injection-pattern list’s hash and the price.
    • Timeouts, 429, 5xx and other errors follow the fallback and retry rules above, inside the same 20 s deadline. TypeSafe has no injection 403, so prompt_injection_blocked does not arise from it; a 422 is a provider_error.
    • The key comes only from TYPESAFE_API_KEY. It is never journaled or logged.

DCA tranche gating (amendment 1)

  • Gate only. For each gated tranche (j = 0..11), arm B’s model may approve the coin’s budget b_ij (×1.0) or skip it. Shrinking by a multiplier in [0.25, 1.0] was allowed until the 2026-10-21 amendment (§14) and is no longer offered. Whatever it does not buy carries to the coin’s next tranche (§5, Carry-forward). It cannot:

    • buy outside the schedule;
    • buy more than b_ij;
    • move budget to another coin;
    • keep budget out of the market past the final tranche;
    • choose a coin;
    • sell.

    Skip is the schema’s reject.

  • The final tranche is not gated. At τ_12 both arms buy each coin’s whole remaining budget. No LLM call is made and no dca_tranche row is written; the collector reads the order from the trades database.

  • One decision per tranche. Exactly one LLM call is made per (strategy, pair, τ_j, dca_tranche), for j = 0..11. The decision is cached, replayed after a restart, and journaled once, as for signals.

  • Independent calls, fixed order. The coins of a due tranche are decided one call at a time, in the basket’s rank order.

    • Every call sees the portfolio as it stood at τ_j, before any of the batch’s buys, and never sees the other coins’ decisions in the same batch. This keeps each decision independent of call order.
    • Sequential calls also kept a batch inside OpenRouter’s free-tier limit of 20 requests a minute [OPENROUTER-LIMITS-01]. TypeSafe documents no rate limit [TYPESAFE-SYSTEMONE-01]; a 429 is retried like any other.
  • Fallback for a DCA tranche: buy the full tranche (fail to arm A).

    • Which is the action. Buying is the default: arm A takes it, and the schedule commits to it before T0. Skipping or shrinking is the LLM’s treatment.
    • The rule. On any fallback cause in the list above, arm B buys at multiplier 1.0 and journals the cause. A buy at ×1.0 deploys the coin’s whole budget b_ij, so it also deploys any amount carried from earlier skips or shrinks. After it, B’s cumulative spend on the coin equals arm A’s again.
    • What “fail-closed” means here. The gate closes on the LLM’s influence, not on the order: no budget is deferred without a valid, non-fallback LLM decision.
    • Why:
      1. A fallback is not an LLM judgement (§3), so it must not create a new difference from arm A. Skipping on a fallback would book provider outages in D1 as if the LLM had chosen to defer. A fallback ends any deferral; that deferral’s outcome is credited to the earlier LLM decisions that made it (ρ in §3), and the fallback row itself has w = 0.
      2. The bias runs only towards “no difference”. A fallback adds no deferral of its own (w = 0), so it can only remove LLM decisions from D1 and H3-D, never add a difference. Earlier LLM deferrals and their ρ stay in both, so a run with outages can still read “better” or “worse”, but not because of the outage. The 10% fallback-rate void condition bounds the dilution.
      3. It removes an attack lever. A hostile headline that triggers the provider’s injection block (HTTP 403) [OPENROUTER-GUARDRAILS-01] can make B buy its carried budget now, but cannot force it to skip. The largest such order is b_ij ≤ (j + 1) × s, which never takes B’s cumulative spend on the coin above arm A’s. The remaining lever, voiding the run through the fallback rate, is closed by the retry rule below: after a 403 the retry omits the headline block.
      4. The exposure it adds is never more than arm A’s, which the experiment holds anyway, in paper money.
    • The signal family keeps its rule (entries rejected, exits approved). There an entry is the strategy proposing new risk, and declining it is the conservative default.
  • Retries inside the execution window. A failed call is retried, not a re-ask, because no valid output was received.

    • Retried causes: timeout, provider error (HTTP 429, 5xx or another non-403 error), served_model_mismatch and prompt_injection_blocked. Never retried: invalid_output (a retry would be a re-ask), budget_exhausted and gate_error; they fall back at once.
    • Schedule: after a retried failure no order is placed. The coin’s call is attempted again on the first bot loop at or after τ_j + 1 h, + 3 h, + 6 h and + 12 h, each attempt with its own 20 s deadline. If the fifth attempt also fails, that attempt is the fallback: B buys the whole b_ij at ×1.0 at once, inside the 24 h window.
    • Inputs: every attempt sends the inputs built at τ_j (portfolio, market summary and headlines as of τ_j), so the decision is still one decision about τ_j. After a prompt_injection_blocked response, later attempts for that tranche date send the prompt with the headline block empty, so one hostile headline costs that batch its headlines, not its decisions.
    • Journaling: still one dca_tranche row per (pair, τ_j), written at the final attempt. It records the attempt count, each failed attempt’s cause and time, and whether the headline block was omitted. The fallback rate counts tranches whose final attempt was a fallback; failed attempts that a retry recovered are reported per cause beside it.
    • B’s order then goes out at the successful attempt’s time, later than arm A’s; the fill term of the D1 decomposition carries that price difference.
  • Hooks, and exceptions inside them. freqtrade 2026.9 calls custom_stake_amount only when no trade is open for the pair [FREQTRADE-BOT-SOURCE-01]. So arm B gates a coin’s opening tranche (its first filled tranche, which follows any skipped ones) in custom_stake_amount, and every later tranche in adjust_trade_position. freqtrade’s own exception defaults break the fallback rule on both paths [FREQTRADE-STRATEGY-WRAPPER-01]:

    • an exception escaping custom_stake_amount makes freqtrade buy the proposed stake, the config stake_amount s, not b_ij, so a carried amount stays carried without any decision;
    • an exception escaping adjust_trade_position places no order and is suppressed, so the tranche is silently not bought and may leave no dca_tranche row for the void conditions to count.

    Pinned: both DCA hooks catch every exception themselves, across their whole body (input building, the LLM call, the journal write and the stake arithmetic). On an exception the hook journals a gate_error row and returns b_ij itself, i.e. the pre-registered fallback, buy the whole budget at ×1.0. An exception that escapes either hook is a defect, not a fallback. Validation check 7 tests a raising hook on both paths.

  • Schema. A DCA row has its own side value, dca_tranche. On a fallback it is approve with applied_multiplier 1.0 and size_multiplier null, because the multiplier is not the model’s (pack issue #36). Every dca_tranche row also records the coin’s budget b_ij, its carried part b_ij − s and the ordered stake x_ij, so w_ij is read from the journal. The ordered stake is checked against the trades database’s filled stake with a tolerance of one lot step × fill price per order (§3, Budget).

What is held identical across a strategy’s arms

  • Strategy code and parameters. Arm B is a thin subclass. It adds the custom_stake_amount, confirm_trade_entry and confirm_trade_exit hooks above, each calling super() where the strategy defines it. It also overrides these, none of which changes a signal:

    • bot_start: calls super(), then runs the startup checks and builds the gate.
    • informative_pairs: adds one higher timeframe per pair for the LLM’s market summary. Arm B therefore downloads candles arm A does not, but the strategy’s indicators and signals never read them.
    • populate_exit_trend and custom_exit: return the strategy’s own result unchanged, and log an undeclared exit tag or custom_exit string as a declaration violation.

    For DCA, arm B gates a coin’s opening tranche in custom_stake_amount and every later tranche in adjust_trade_position (DCA tranche gating). It changes only the stake (m × b_ij, or no order on a skip, with the rest carried to the coin’s next tranche), never the schedule.

  • Pair whitelist, computed once at T0 − 1 day and then frozen:

    1. Take the Conservative screening snapshot (skill 01).
    2. Keep pairs with a /USDT spot market on the chosen exchange.
    3. Keep pairs whose 30-day median daily quote volume is ≥ USD 5,000,000.
    4. Rank by that volume and take the top 10.

    Each strategy trades the whitelist intersected with its own pair universe: TrendCash takes BTC/USDT and ETH/USDT, and DCA takes the top k in rank order (DCA mechanics). That pair set is identical across the strategy’s arms and baselines. The snapshot manifest hash is shown on the dashboard.

  • Capital and fees:

    • 10,000 USDT paper wallet per arm, in all four bots (owner decision, 2026-10-08, pack issue #58, erratum in §14; it was 5,000 from amendment 1 on 2026-10-07, and 1,000 before that). One wallet size for every arm keeps the DCA and TrendCash stakes on the same scale. The larger wallet gives the DCA arms about 128% margin over the exchange minimum instead of 14% (DCA mechanics). It does not by itself stop TrendCash’s capital skips, because the signal-family stake scales with the wallet; TrendCash’s lower headroom does that.
    • A fixed stake rule in every arm, never “unlimited”, so cash freed by shrinking does not compound into later stakes. Amendment 1 adds headroom to the signal family’s stake.
      • Signal family, sizing parity with the gate. stake_amount = floor(dry_run_wallet × capital.headroom / max_open_trades) USDT, with capital.headroom 0.90 unless the strategy pre-registers a lower one (TrendCash: 0.50, erratum 2026-10-08, §14), and tradable_balance_ratio 0.99. This is exactly the formula and headroom gate 0.4.0 uses to size its backtests (skill 04), so the gate scores the stake the arms trade.
        • The parity rule. A signal-family arm runs the setup.stake_amount printed in the gate report that cleared it for the experiment (for a control, its --control-label re-run), and that report must have been run with the arm’s own dry_run_wallet, max_open_trades and capital.headroom. If the arm’s stake differs from the report’s, the strategy does not run. A lower pre-registered headroom is allowed only if the gate report used it too.
        • TrendCash, recomputed for the 10,000 USDT wallet and its pre-registered headroom 0.50 (erratum 2026-10-08, §14): floor(10,000 × 0.50 / 2) = 2,500 USDT. Its volatility sizing then proposes 0.5 × to 1.0 × that, 1,250 to 2,500 USDT. History: attempt 1 (gate 0.2.0, 1,000 USDT wallet) had 495 = floor(990 / 2); attempt 2 (gate 0.4.0, 5,000 USDT wallet, headroom 0.90) had floor(5,000 × 0.90 / 2) = 2,250, at which capital bound (TrendCash control, below).
      • Why the headroom. freqtrade checks the free balance against the configured stake before custom_stake_amount runs, and skips the entry when the balance is short [FREQTRADE-WALLETS-SOURCE-01]. This cost TrendCash 36 of 385 entries in its gate run (strategies/trendcash/RESULTS.md, pack issue #33). With the default 0.90 headroom under the 0.99 tradable ratio, an arm with two slots must realise a loss of about 9% of its wallet, plus fees, before that can happen, and any entry it still skips is counted (below). For TrendCash 9% was too little: attempt 2 refused about 50% of its holdout entry attempts. With 0.50 the loss must reach about 49.5%, 1 − 2 × 2,500 / 9,900 (erratum 2026-10-08, §14; strategies/trendcash/PREREG.md, attempt 3 addendum).
      • DCA family: its own pre-registered tranche formula (DCA mechanics), not capital.headroom. The headroom protects a strategy that realises losses from finding its free balance short of the next stake. A DCA arm never sells, and freqtrade counts open stakes at cost in the available balance [FREQTRADE-WALLETS-SOURCE-01], so a DCA arm’s free balance falls only by its own scheduled buys and their fees. Those are known before T0, and the DCA budget leaves room for them under the 0.99 tradable ratio. Both DCA arms use the same formula, so parity within the strategy holds.
      • Skips are counted. freqtrade logs any entry it still skips this way as “Unable to create trade” [FREQTRADE-BOT-SOURCE-01]. The collector counts these per arm, and the dashboard shows the count.
    • Same max_open_trades, fee and fill model.
    • Fee: freqtrade’s fee setting is 0.001 (0.10% per side, the pack default, not an exchange quote) in all four bots. In dry-run, freqtrade charges the configured fee [FREQTRADE-EXCHANGE-SOURCE-01].
    • Fills: freqtrade’s own dry-run simulation, with the same order types in a strategy’s two arms. A simulated market order fills by walking the order book [FREQTRADE-EXCHANGE-SOURCE-01]. No extra slippage is added to arm fills.
    • Baseline costs: the computed baselines use fee_rate 0.001 and slippage_rate 0.0005 per side, the gate’s defaults.
    • Minimum-stake assertion. In dry-run and live, arm B’s startup asserts, for every whitelisted pair, that 0.25 × the smallest stake the strategy can propose (stake_amount unless the strategy declares a smaller one) is at least the exchange minimum.
      • Otherwise freqtrade’s validate_stake_amount would drop the trade or raise the stake to the minimum, and the journaled multiplier would not be the applied one [FREQTRADE-WALLETS-SOURCE-01].
      • The fix is a larger stake, never a lower floor.
      • If prices move after startup, applied_multiplier still records what freqtrade applied.
      • For DCA, both arms assert the basket rule’s inequality at startup.
  • Market and timing: same exchange and data feed, same timeframe, same T0 and T_end. Arms run on the same host with the same restart policy. Every restart is logged with timestamp and downtime.

    • Run-start gate (§14, 2026-10-09). Each TrendCash arm starts with one freqtrade global pair lock that ends exactly at T0, so TrendCash cannot trade before T0; DCA needs no lock, because its schedule already starts at T0.
    • How the collector reads restarts and operator state changes (erratum 2026-10-08, §14). The ledger is each arm’s freqtrade log file (--logfile, mounted read-only), checked against /api/v1/health [FREQTRADE-WORKER-SOURCE-01] [FREQTRADE-HEALTH-SOURCE-01].
      • Process restart: a freqtrade.worker line Changing state to: RUNNING (no from part). Each line is one restart, so several restarts between two polls are each counted. A restart is downtime (condition 3) but does not void by itself. The collector cannot tell a restart made by the restart policy from an operator’s docker restart, so an operator restart must still be declared as a manual intervention (condition 4, unchanged).
      • Operator state change: a freqtrade.worker line Changing state from <OLD> to: <NEW>, written by /stop, /start, /pause and reload_config. Each one inside [T0, T_end] is a manual intervention and voids the run (condition 4). The one exception is a stop the bot makes itself: OperationalException. Stopping trader ... followed by Changing state from RUNNING to: STOPPED. It is listed, not voided, and the operator’s /start after it voids.
      • Cross-check: bot_startup_ts from /health is rewritten at every startup, and each change must match a state-change line in the log. bot_start_ts is the bot’s first start, never changes and cannot show a restart. A collector restart is not a bot restart.
      • Downtime is time the bot is shown not to be looping. A restart or state-change episode runs from the bot’s last loop before it (the later of its last RUNNING or PAUSED heartbeat line and the last loop time a good poll returned) to the startup line after it. A failed poll (no answer, no loop time while starting or reloading, or a loop older than the stall limit) opens an episode at the last poll, or, after a collector gap, at the bot’s last loop that the log or the poll shows. It closes at the bot’s startup or the next good poll. Overlapping episodes are merged. Only episodes inside [T0, T_end] count, and restarts and state changes outside it are not listed.
      • Unobserved time is time the collector itself did not poll. It is reported per arm, separately from downtime, and flagged above 24 h. It is not downtime.
      • Restart detection unavailable: the arm’s ledger cannot be shown complete over [T0, T_end] when the log is unreadable, is not read through T_end, was rotated past its unread end or shrank, or has no new line for 10 minutes while the bot loops; when /health has no bot_startup_ts on more than 3 consecutive polls; or when the marker moves with no state-change line in the log. The arm is flagged, and the readout publishes no verdict except Void (verdict rules, Unavailable).
  • Risk controls:

    • Protections are identical within each strategy’s two arms, and react to each arm’s own history, so lock events are journaled per arm.
      • TrendCash keeps its own protections (MaxDrawdown, StoplossGuard, CooldownPeriod).
      • The DCA arms run no protections. A protection lock would skip scheduled tranches based on the arm’s own P&L, turning the fixed schedule into a timing rule, and timing is what arm B’s gate alone is meant to add.
    • stoploss_on_exchange is false in every arm, and arm B’s startup asserts it. freqtrade cancels the exchange stop before it calls confirm_trade_exit [FREQTRADE-BOT-SOURCE-01], so a held exit would leave only arm B without a stop. Both arms use the bot-side stoploss.
    • Spot only, long only.
  • No manual intervention:

    • Telegram is disabled and force_entry_enable is false.
    • The REST API is reachable only from the bot host’s private network (§7, Architecture).
    • Any manual action voids the run.

Strategies (amendment 1)

StrategyRoleFamilyPairsValidationIn the headline
DCAPrimaryDCA (baseline family)Top k of the frozen whitelist (DCA mechanics)Baseline-family checks (Validation of DCA, below)Yes (n = 1)
TrendCashLabelled control: control (failed validation gate)SignalFrozen whitelist ∩ {BTC/USDT, ETH/USDT}Gate attempt 1: fail, with check 1 pass; control label as belowNo
BTCETHGrid_v3Dropped——Never gated—
  • The strategy set is frozen by this amendment. No strategy joins after T0. Before T0, only a further dated amendment can add one.
  • Option 3. New families (pack issue #38, and the TrendRider case study, pack issue #37) are pre-registered and gated on the build VPS. They may join a later experiment only.
    • The owner also approved a gate case study of their lab strategy BinHV45_Regime_5m (pack issue #43, 2026-10-07). It is not part of this experiment; whatever its gate result, it is a candidate for a later experiment only.
  • Why BTCETHGrid_v3 is dropped.
    • It was a candidate for comparing the gate on a weak strategy against a sound one, and that comparison no longer exists: the sound candidate, TrendCash, failed the gate.
    • It was never gated, so it could only have joined as a second control, which adds no headline evidence.
    • The 4+3 design limits the lab to two strategies (four bots).

DCA mechanics (pre-registered)

  • Basket and stake. The basket is the top k pairs of the frozen whitelist in rank order, with each coin getting stake s per tranche.

    • The DCA budget is D = 0.98 × W0 = 9,800 USDT, and s_k = floor to the cent of D / (13 × k) USDT. D leaves 100 USDT under freqtrade’s default 0.99 tradable-balance ratio (9,900), so fees and rounding never trip the free-balance check [FREQTRADE-WALLETS-SOURCE-01]. The other 100 USDT is the 1% that ratio keeps back. (Erratum 2026-10-08, §14: at the 5,000 USDT wallet these were 4,900, 50, 4,950 and 50.)
    • M_k = the largest freqtrade minimum stake among the top k pairs, get_min_pair_stake_amount(pair, price, stoploss), using the DCA strategy’s own stoploss and the T0 − 1 day close [FREQTRADE-EXCHANGE-SOURCE-01].
    • k is the largest k ≤ 10 with 0.25 × s_k ≥ 1.10 × M_k. The 10% headroom covers moves in price-dependent minimums and exchange filter changes during the run.
    • The opening-entry minimum (with the strategy’s stoploss) applies to every tranche. A tranche that follows a skipped first tranche opens the position, and freqtrade uses the lower position-adjustment minimum (stoploss 0.0) only when a trade is already open [FREQTRADE-BOT-SOURCE-01].
    • If no k ≥ 1 qualifies, DCA does not run, and the experiment needs a new amendment before T0.
  • What the basket rule gives today.

    • On 2026-10-07 the major Binance USDT spot pairs checked had a 5 USDT minimum notional, and DOGEUSDT 1 USDT; ccxt maps this to the market’s minimum cost [BINANCE-EXCHANGEINFO-01].
    • freqtrade multiplies the minimum cost by 1.05 / (1 − |stoploss|), capped at 1.5, and uses 1.5 when the stoploss is −100% [FREQTRADE-EXCHANGE-SOURCE-01]. So M ≈ 7.50 USDT.
    • At 10,000 USDT per arm the rule gives k = 10, the cap (erratum 2026-10-08, §14):
      • s_10 = floor_cents(9,800 / 130) = floor_cents(75.3846) = 75.38 USDT;
      • the smallest order, a ×0.25 shrink, is 0.25 × 75.38 = 18.845 USDT;
      • the bar is 1.10 × M_10 = 1.10 × (5.00 × 1.5) = 1.10 × 7.50 = 8.25 USDT, where 1.5 is freqtrade’s reserve at stoploss −100%;
      • 18.845 ≥ 8.25, with about 128% to spare. At 5,000 USDT per arm it was s_10 = 37.69, a 9.4225 USDT smallest order and about 14% to spare.
    • Expected count: 13 × 10 = 130 tranches, of which 12 × 10 = 120 are gated LLM decisions; the 10 final-tranche buys are fixed by the carry-forward rule. If a top-10 pair’s minimum on the T0 − 1 day run is higher than these, the rule takes the largest k ≤ 10 that clears it; it is never relaxed.
    • For comparison, 5,000 USDT per arm gave k = 10 with s = 37.69, 1,000 USDT per arm gave k = 2 (s = 37.69, 26 tranches), and k = 10 needs about 4,380 USDT per arm (s ≥ 33.00).
  • Schedule.

    • T0 is at 12:00:00 UTC on any weekday: Monday 2026-10-26 12:00:00 UTC (§14, 2026-10-21). Tranche j (j = 0..12) is due at τ_j = T0 + 7j days, so every τ_j is a Monday and the last is on day 84, 2027-01-18, 6 days before T_end (2027-01-24 12:00 UTC).
    • Each due tranche buys every basket coin once. The strategy runs on the 1h timeframe.
    • Why this time: it is a weekday, mid-session, away from the 00:00 UTC daily-candle rollover. It matches the DCA baseline’s days 0, 7, …, 84, so arm A and the baseline share a schedule. It was fixed before any data and is not a tuned parameter.
  • Execution window.

    • A tranche’s order is placed on the first bot loop at or after τ_j, and no later than τ_j + 24h. After that the tranche is missed in that arm, and its budget carries to the coin’s next tranche in that arm, as any unspent budget does. A missed final tranche leaves its budget idle until T_end.
    • Missed tranches are reported per arm. A decision whose own tranche, or whose next tranche, arm A missed is unmatched and excluded from H3-D (§3, Matched).
    • Downtime long enough to miss a tranche also counts towards the downtime void condition (§3, condition 3).
  • Carry-forward (owner decision, 2026-10-07; it replaces the draft’s no-carry-forward rule). Unspent budget from a coin’s tranche rolls into that coin’s next tranche, in both arms.

    • Budget recursion. b_i0 = s, and b_i,j+1 = s + (b_ij − x_ij), where x_ij is the stake ordered for (i, j). Arm B orders x_ij = m_ij × b_ij at a gated tranche. Arm A orders b_ij, which is s unless it missed the previous tranche.
    • The final tranche invests everything remaining. At τ_12 each arm orders b_i,12 for every coin, with no LLM call. Per coin, each arm therefore invests 13 × s in all, unless a final tranche is missed, a pair is delisted, or the window is truncated (§5, LLM provider).
    • A buy at ×1.0, including every fallback, deploys the whole b_ij, carried part included.
    • Per-tranche stake bound. s ≤ b_ij ≤ (j + 1) × s. So arm B’s largest possible single order is b_i,12 ≤ 13 × s = 979.94 USDT (erratum 2026-10-08, §14; 489.97 at 5,000), when the coin was skipped at every gated tranche. Its smallest non-zero order is 0.25 × b_ij ≥ 0.25 × s, so the basket rule’s inequality on s still covers every order, carried or not.
    • Capital. Both arms’ total orders never exceed D = 13k × s ≤ 9,800 USDT. freqtrade counts open stakes at cost in the available balance [FREQTRADE-WALLETS-SOURCE-01], so before any order for coin i the available balance is at least that coin’s remaining budget plus about 100 USDT, less fees (erratum 2026-10-08, §14; 4,900 and 50 at 5,000). validate_stake_amount caps a stake at the available balance [FREQTRADE-WALLETS-SOURCE-01], which a carried order stays under. Pack issue #36 verifies in the 2026.9 image that nothing else caps a custom stake at the config stake_amount, for the hook it uses (opening entry and position adjustment), and validation check 5 tests it.
    • No carry between coins, and nothing carries past T_end.
    • What it changes. The gate decides when each part of a coin’s budget is bought, within the 13 weeks, not whether. H1-D therefore measures timing, and H3-D compares each deferral with buying at the skipped date (§3).
  • Never sells. There is no exit signal, ROI is disabled, there is no trailing stop, and the stoploss is −1.0 (−100%), which a spot price above zero never reaches.

    • Holdings are not sold at T_end. They are valued at liquidation value (§3).
    • Any exit of a DCA position, including an emergency stop by hand, is a manual intervention and voids the run (§3, condition 4).
  • Fees and fills: as in “Capital and fees” above, identical in both arms. B’s fills can differ from A’s because of latency and the order book. The fill term of the D1 decomposition reports this, and the latency-cost diagnostic (§6) pairs each B fill with A’s fill for the same tranche.

  • Delisting or long halt, decided at τ_j. The test uses only what the bot can see at τ_j: a basket pair is delisted from τ_j on, irrevocably, when at τ_j the exchange’s market list no longer shows it as an active spot market, or the bot has no closed hourly candle for it in the 48 h before τ_j. Each arm’s DCA strategy evaluates the flag at τ_j and records it in its journal for every remaining tranche of the pair (pack issue #36). The collector uses those recorded flags and never re-derives them after the fact, so the bot and the collector cannot disagree. If the two arms’ flags differ for a tranche, the arm that bought it has a fill and the other has none, so the decision is unmatched (§3). For a delisted pair, in each arm that flagged it:

    1. its remaining tranches are not bought, no LLM call is made for them, and their budget stays idle, including any amount arm B carried. For ρ in §3, the missing next tranche has r = 0;
    2. its holdings are valued at the last available hourly close at or before the window end, net of the exit fee, and counted as USDT from that close on, as the baselines do. Its tranche returns r_ij end at that price.

    Nothing is sold by the bot and no one intervenes. A halt that does not meet the test at τ_j follows the execution-window rule: if the order then fails, the tranche is missed and its budget carries. The collector’s retrospective 48 h rule still serves the baselines and data-gap reporting, never the arms’ tranches. A screening verdict change during the run changes nothing, because the whitelist is frozen at T0 (§11).

Validation of DCA (baseline family)

DCA is the yardstick that skill 04 measures strategies against (check 5). The gate cannot judge DCA itself, so docs/PACK_SPEC.md Principle 3 is amended (amendment 1) with a baseline-family exception. These checks replace the gate for DCA. The results go in strategies/dca/VALIDATION.md, committed before M1 (pack issue #36).

Gate checkFor DCAReplacement
1 Lookahead and recursiveN/A: entries depend only on the candle’s timestamp, and there is no indicatorA unit test: randomising every OHLCV value leaves the entry decisions and tranche stakes unchanged
2–4 Walk-forward, regimes, DSR/PBON/A: no parameter is chosen from data, and there is no grid. Declared trials 0The schedule, weights, basket rule and stake are fixed in this PRD
5 Beat Hold and DCAN/A: DCA is the baseline, and comparing it with itself means nothingHold is still reported beside it

All of these mechanical tests must pass:

  1. Schedule: exactly 13 tranches, at τ_j and nowhere else; the 24 h execution window; missed beyond it.
  2. Stakes and carry-forward: both arms follow the budget recursion of DCA mechanics, tested with runs of 1, 2 and 11 consecutive missed or skipped tranches. Arm A orders b_ij, which is (n + 1) × s after n consecutive missed tranches; arm B orders m × b_ij; a fallback deploys the whole b_ij; the final tranche orders the whole remaining budget with no LLM call; no budget moves between coins.
    • Each coin’s ordered total is 13 × s to the cent. Its filled total is 13 × s less the truncation residue, within one lot step × fill price per order.
    • max_entry_position_adjustment is −1 or at least 12, so freqtrade never stops a coin’s later tranches [FREQTRADE-BOT-SOURCE-01].
  3. No exits: over a synthetic 99% crash and a 10× rally, no exit of any kind occurs before T_end.
  4. Minimum stake: both arms’ startup asserts 0.25 × s ≥ 1.10 × M_k for every basket pair.
  5. Capital: two backtests of 13 tranches execute every order with zero capital skips: one with every tranche approved (13k buys of s), and one in which arm B skips every gated tranche, so each coin’s final order is 13 × s.
  6. Replay: arm A, backtested over a past 90-day window, matches the computed DCA baseline (baseline_equity, same pairs, s and fees) within 0.5 pp at the window end. This checks the mechanics, not returns. It is not a gate attempt, writes no ledger row, and is never quoted as evidence of returns.
  7. Gate wiring (arm B): one decision per (pair, τ_j) for j = 0..11 and none at j = 12; replay after restart, including the carried budget; fallback buys the whole b_ij at 1.0; journal rows validate against the schema; a mock-LLM smoke run.
    • Raising hooks, in the freqtrade 2026.9 image: make the gate raise inside (a) custom_stake_amount on an opening tranche that follows a skip, so b_ij = 2 × s, and (b) adjust_trade_position on a later tranche with a carried budget. In both cases the test asserts that freqtrade places an order of exactly b_ij, that exactly one gate_error row is journaled for the tranche, and that the next tranche’s budget is s.
  8. Config: the spot-only config lint (skill 03) passes.

Why this does not weaken Principle 3.

  • Principle 3 stops a strategy whose edge is unproven from being presented as a candidate, and the gate measures an edge against Hold and DCA.
  • DCA claims no edge, and the experiment does not ask whether DCA is good. It asks whether the LLM gate improves on DCA.
  • What can go wrong in a baseline is mechanical: a wrong schedule, a wrong size, a hidden exit. The tests above check exactly that.
  • Principle 3 is still amended to say so explicitly, rather than relying on reading DCA as “not a candidate”.
  • The exception covers paper trading only. Real money stays with skill 07.

TrendCash control

  • Label: exactly control (failed validation gate), everywhere it is shown: the dashboard, the API and the readout.
  • Basis.
    • Gate attempt 1 (2026-10-07) gave verdict fail and eligibility none, with check 1 (lookahead and recursive) pass. Check 1 passing is the precondition for a control (skill 04).
    • The owner granted control status on 2026-10-07 (pack issue #35).
  • Recording it: a re-run under gate 0.4.0, approved by the owner on 2026-10-07. Done: attempt 3 (2026-10-08) is the recording of record. Control status needs a gate report that carries the label, so the owner re-ran the pre-registered attempt (strategies/trendcash/PREREG.md) with --control-label "<owner's reason>" under gate 0.4.0 (pack PR #39) and committed the report and ledger row.
    • It is a new, counted ledger attempt. The gate version is part of the fingerprint (skill 04), so the re-run cannot reuse attempt 1. It runs on spent windows, which is acceptable for a control, whose eligibility never depended on a pass. Its full report is published beside attempt 1, and both count in the ledger.
    • Attempt 2 (history, superseded). Gate 0.4.0 at the 5,000 USDT wallet then in force, stake floor(5,000 × 0.90 / 2) = 2,250. It returned known_weak_control_paper_only, but capital bound (below), so attempt 3 replaced it for the deployed sizing.
    • Attempt 3 (2026-10-08, erratum, §14, pack issue #58), the deployed sizing. Inputs: identical to attempt 2 except dry_run_wallet 10,000 (the experiment’s wallet) and --gate-config with capital.headroom 0.50 (tighten-only), recorded with their sha256 in a dated PREREG.md addendum before the run. The rule was: setup.stake_amount must be floor(10,000 × 0.50 / 2) = 2,500, with setup.capital_headroom 0.5 and max_open_trades 2, and the result known_weak_control_paper_only with check 1 pass; otherwise TrendCash does not run.
    • Result: the rule is met. Verdict known_weak_control_paper_only, check 1 pass, stake 2,500, zero capital skips in checks 2 to 5 and the holdout; holdout −1.24% over 40 trades against Hold +27.45% (strategies/trendcash/RESULTS.md, ledger row 3 of records/oos-ledger/TrendCash.jsonl). TrendCash runs as the labelled control.
  • Verdict: the signal-family rules of §3, unchanged. It is excluded from n, k and every headline, and never promotable to real money (PACK_SPEC Principle 3).
  • Deployed config: strategies/trendcash/config.control-10000.json, pinned in PREREG.md: as frozen there, except dry_run_wallet 10,000 instead of 1,000 (the experiment’s wallet) and stake_amount 2,500 instead of 495: floor(10,000 × 0.50 / 2), the gate 0.4.0 formula above with TrendCash’s pre-registered headroom (sizing parity), which attempt 3’s report must show as setup.stake_amount (erratum 2026-10-08, §14; it was 5,000 and 2,250 after attempt 2). The change is disclosed on the dashboard.
    • Why it was re-sized (attempt 2, 2026-10-08). At 2,250 with headroom 0.90, gate 0.4.0 refused 31 entry attempts against 31 holdout trades, about 50%, and 12.5 to 18.3% per check. Arm B’s vetoes change its free balance, so such skips are path-dependent and would differ between arms. Attempt 3’s re-sizing (wallet 10,000, headroom 0.50, stake 2,500) brought the skips to zero (erratum 2026-10-08, §14).
    • 0.25 × 0.5 × 2,500 = 312.50 USDT clears the exchange minimum (erratum 2026-10-08, §14; it was 0.25 × 0.5 × 2,250 = 281.25).
    • The thin arm-B subclass declares its exit tags (llm_gate_exit_tags) explicitly, as Exit gating requires; the mixin attribute shadowing of pack issue #32 is fixed (pack PR #44).
  • Expected outcome: the gate’s 180-day holdout had 36 trades in attempt 1 and 31 in attempt 2, so 90 days will likely give fewer than 20 matched vetoes. H3 is then not evaluable, and “Inconclusive” is the expected verdict.

LLM inputs (arm B)

  • Signal: pair, strategy, entry or exit tag, indicator snapshot at signal time.
    • For DCA, the signal is the scheduled tranche: pair, tranche j of 13, the scheduled stake s, and the coin’s budget b_ij with its carried part.
    • The DCA prompt states that a skipped or shrunk amount is carried to the coin’s next tranche, that approving deploys the whole budget, and that the final tranche invests everything remaining without a decision.
  • Market context: summary of recent candles on the signal timeframe and one higher timeframe. Raw candle dumps are not allowed.
  • Portfolio state: open trades, exposure, current drawdown, remaining slots. For DCA, it is per-coin units, average cost and carried budget, idle cash, and tranches done, all as of τ_j (DCA tranche gating).
  • Headlines: last 24h from the pre-registered free RSS feeds in strategies/llm_gate/rss-feeds.json (CoinDesk, Cointelegraph, The Block, Decrypt; owner decision, 2026-10-08, pack issue #65), deduplicated. Headlines are untrusted content: the prompt treats them as data, never as instructions.
    • Before the prompt is built, headlines matching a local injection-pattern list are dropped and counted in the row’s headlines_dropped_injection. Otherwise one hostile headline would make the provider’s injection guardrail reject every entry [OPENROUTER-GUARDRAILS-01].
    • The pattern list is frozen at T0 with the prompt.
    • Titles are NFKC-normalised and stripped of zero-width and other format characters before screening and prompting. The screening copy also folds accents and look-alike letters, so Unicode tricks do not slip past the list.
    • Feeds refresh on a background thread; a decision reads the cached list and never fetches. A slow feed therefore never delays arm B’s loop, whose stoploss and ROI checks run in the same thread as the hooks [FREQTRADE-BOT-SOURCE-01].
  • Screening status: the coin’s profile verdict and its sources.
  • With the typesafe backend the same inputs are sent as the System One state (§5, Hard rules for arm B, Decision backend), the headlines in its untrusted_headlines after the same pre-filter.
    • Disclosed limitation (2026-10-21, pack issue #77): adversarial text in state can move the model’s verdict (owner’s verification of the API). The headline pre-filter and the fallback rules are therefore kept unchanged, but a hostile headline the filter misses could still tilt a decision.

LLM provider and budget

  • Decision source: TypeSafe Jev (amendment 2026-10-21, pack issue #77). Model jev-1.13.0, hosted by TypeSafe and called through its System One API, POST https://api.typesafe.ai/v1/systemone with a bearer key [TYPESAFE-SYSTEMONE-01]. It is a decision-only model: each call asks one choice question, approve or skip, and gets back the choice, a confidence and the probability of each option. It cannot return free text. The gate’s LLM_GATE_BACKEND=typesafe mode implements it (§5, Hard rules for arm B, Decision backend), and skill 06 documents it.
    • Pinned: backend typesafe, model jev-1.13.0, the two question files (their hashes are the rows’ prompt_sha256), the request config hash and the 20 s timeout, recorded by experiments/llm-gate-ab/freeze.py.
    • Why it replaced the M1 choice. At M1 the owner chose the free OpenRouter model nvidia/nemotron-3-super-120b-a12b:free with provider slug nvidia and fallbacks off; its smoke eval (experiments/llm-gate-ab/smoke/evidence/provider-smoke-2026-10-08.md, pack PR #67) had 43 calls, all schema-valid (owner decision, 2026-10-08, pack issue #65). Before T0 that free endpoint was retired: every call in the 2026-10-20 smoke returned HTTP 404. The owner will not pay for an LLM, and a local 3B model on the 4 vCPU lab VPS took about 105 s per gate-sized prompt, against the pinned 20 s timeout. The OpenRouter backend stays in the gate’s code, unused by this run.
    • Disclosed limitations. Arm B depends on one hosted service: an outage or a withdrawn model is a fallback and counts against the void thresholds (§3), as for any provider. Adversarial text in the state can move the verdict (§5, LLM inputs). The model gives no rationale, so a decision can be audited only through its inputs, choice and probabilities.
  • Budget:
    • The owner’s separate USD 5 TypeSafe credit, split equally across the strategies at T0: USD 2.00 each for DCA and TrendCash (USD 4.00 allocated, USD 1.00 kept unallocated, owner decision 2026-10-22). It replaces the OpenRouter USD 25 cap (USD 12.50 each) of amendment 1.
    • Each strategy’s B arm enforces its own sub-cap, and H1 and H1-D deduct only that strategy’s spend.
    • The gate charges each answered call input tokens × USD 0.042 per million, the price the owner has (pack issue #77); output tokens are free of charge [TYPESAFE-SYSTEMONE-01]. At a few thousand input tokens per call the caps should not bind.
  • Before T0: the M2 smoke run’s burn rate, from that real per-call cost and projected over 90 days, must be ≤ 60% of each sub-cap.
  • During the run: an alert fires if a strategy has spent 50% of its sub-cap before day 45.
  • If the sub-cap is hit at time t_cap: B falls back and the dashboard marks “budget-capped from t_cap”. In the signal family, entries are rejected and exits approved; DCA tranches are bought at 1.0, which deploys each coin’s carried budget too.
    • If t_cap ≥ day 75, every metric for that strategy, including A and the baselines, is computed on [T0, t_cap].
    • If t_cap < day 75, the strategy’s run is void.

6. Metrics

Per arm:

  • Net return; exposure-adjusted return (net return ÷ time-averaged fraction of equity in positions).
  • Max drawdown, Sortino and profit factor.
  • Trade count, win rate, exposure time and average trade duration.

Arm B only (signal family):

  • Approval and veto rates; size-multiplier distribution.
  • Selection vs sizing effect (§3), with R(B_vo) and MDD(B_vo) next to the realised figures.
  • H3 statistics: Δ3, veto loss rate vs A’s base loss rate, match counts.
  • Exit-veto value: for each trade with a non-fallback rejected exit, B’s realised net exit return minus the net return at the first vetoed exit’s proposed_rate. Reported as a mean with CI; not part of the verdict.
  • Slot effects, both directions:
    • slot-freed: trades B took that A could not, because A’s slots were full;
    • slot-blocked: trades A took that B missed because B was still holding after an exit veto.
  • Latency cost: for trades on the same pair and signal candle in A and B, the mean entry fill-price difference B − A.
  • Latency p50/p95, cost per decision, cumulative cost, and fallback count by cause. The LLM call itself (at most 20s per gated signal) still runs inside arm B’s loop; that is the only blocking cost arm A does not have, and the latency figures measure it.

DCA family (amendment 1), both arms unless stated:

  • Final value V and net return R (§3), with Hold and the DCA baseline beside them.
  • Average cost per coin, and arm B’s ratio to arm A per coin (below 1 means B paid less per unit). It is a diagnostic only: B can show a lower average cost while holding fewer units.
  • Idle cash: hourly series, time-weighted mean, and value at the window end. For arm B it is split into carried budget (deferred, not yet bought), budget not yet due, and other idle cash. Other idle cash is mainly the W0 − 13ks(1 + f) ≈ 190.80 USDT that both arms keep at k = 10, after entry fees (errata 2026-10-07 and 2026-10-08, §14; it was “the 100 USDT (W0 − D)”, then ≈ 95.40 at the 5,000 USDT wallet). It also holds a delisted pair’s idle budget, lot-step truncation residue, a missed final tranche’s budget, and, inside an execution window, the part of x_ij not yet filled. Both arms are fully invested after the final tranche, apart from the 190.80 USDT and those items.
  • Max drawdown on hourly equity, and Sortino.
  • Tranche table: for every (coin, j), arm A’s fill, arm B’s budget b_ij and its carried part, decision, multiplier, fallback cause, rationale (HTML-escaped), B’s fill, and r_ij and ρ_ij once the window ends. The final tranche shows “not gated”.
  • Arm B:
    • skip, shrink and approve counts, per tranche and per coin;
    • W_m and W_all, the multiplier distribution, and each coin’s carried budget over time;
    • H3-D statistics: Δ3-D, the null draws’ median and 5th–95th percentiles, p_better and p_worse, the withheld hit rate against the base hit rate (diagnostic), and the evaluability counts against their minimums;
    • the D1 decomposition, and R(B_vo-D) with skips-only and shrinks-only shares;
    • the rule benchmark table (§3, Pre-registered rule benchmarks): for B and each of the 12 rules, Δ3-D and D1; for each rule, D1(B) − D1(R) as a point estimate, with its one-sided bootstrap bounds for rule 1 only; and the second finding “LLM beyond simple rules”, yes, no or rule better;
    • latency cost: mean B − A fill-price difference over matched tranches;
    • latency p50/p95, cost, and fallback count by cause, as for the signal family.
  • Mechanical cross-check: arm A against the computed DCA baseline at the window end, flagged above 0.5 pp (§5).
  • Missed tranches per arm, and capital-skipped entries per arm (§5, Capital and fees).

Shadow ledger (H3’s outcome source; signal family only):

  • How it is built: at T_end, run freqtrade backtesting of the arm-A strategy over [T0, T_end] on OHLCV for the same exchange and timeframe. It uses the same config, pair set, fixed stake, and fee and slippage model, with max_open_trades unlimited and protections off, so every signal gets an outcome under A’s exit rules. This is not lookahead (§4).
  • Matching: a gate decision matches a shadow trade iff the pair is the same and the shadow trade’s open time is the candle after signal_candle_ts. freqtrade still allows only one open trade per pair, so a signal that fires while the shadow already holds that pair has no match. It is counted as unmatched and excluded from H3 (see the not-evaluable rule).
  • Sanity check: report how many shadow trades match a live A trade, and the mean P&L gap on those. A gap above 0.5 pp per trade is flagged as a ledger fault before the readout.

Run health:

  • Uptime per arm, data-feed gaps, and restarts.
  • Signal candles that fall inside one arm’s downtime are excluded from paired metrics and counted.
  • Any downtime difference above 30 minutes is flagged on the dashboard.
  • Degraded: B’s fallbacks above 10% of its gate calls over any rolling 7 days flag the run as degraded on the dashboard. This is an early warning, not a void condition.
    • In the signal family, frequent fail-closed rejects bias the comparison against B.
    • In the DCA family, frequent fallbacks pull B towards A.

Statistics:

  • 90% CIs from 10,000 bootstrap resamples with a fixed seed.
  • H1, H2, H1-D and H2-D: stationary block bootstrap (mean block 5 days) on daily (B − A) returns. Concurrent trades on correlated coins are not independent.
  • H3: signal-level bootstrap clustered by UTC signal day.
  • H3-D: the randomization test of §3 (10,000 permutations of the 12 gated dates, random.Random(stats.SEED + 4), α = 0.005 per side), not a bootstrap. All decisions of a tranche date share its batch and its market move, so whole dates are the unit that is permuted.
  • “LLM beyond simple rules” (DCA): paired stationary block bootstrap (mean block 5 days) of daily equity returns, B against the replay of rule 1 (skip above the 28-day MA), the one primary benchmark, 10,000 resamples, random.Random(stats.SEED + 5), one-sided α calibrated per side by simulation: 0.05 for yes (5th percentile), 0.01 for rule better (99th percentile); the other 11 rules get no test (§3, Verdict rule).
  • Trade counts will be small (tens per strategy), so wide CIs are expected and shown, not hidden. DCA’s power is stated in §3.

7. Live dashboard (labs.buildeon.solutions)

Must have:

  • Equity curves on one chart per strategy:
    • signal family: A, B, B_vo, Hold and DCA;
    • DCA family: A, B, B_vo-D, Hold and the DCA baseline.
  • Metrics table with CIs.
  • Verdict badge per strategy (Running / Agentic better / Agentic worse / Inconclusive / Void / Unavailable), with its reason. It only changes from Running at T_end, or on void.
  • The run is not blinded (2026-10-08, §14). The live A and B equity curves give the running D1, and B’s public tranche decisions with public prices give the outcome of each skip. Formal statistics, p-values and bounds are published only in the readout. After T_end the badge reads “Computing final readout” until the final prices, polls and outcomes are in; the readout is then published once and frozen. While it is Computing, the reason names what is missing and the grace deadline, T_end + 7 days. If the data recorded by that deadline is still incomplete, the badge reads Void instead (§3, void condition 7), and that readout is frozen too.
  • Two strategies with different metric families (amendment 1).
    • The collector, API and page carry each strategy’s family. They show the signal-family panels (shadow ledger, exit vetoes, slot effects) only for TrendCash, and the DCA panels only for DCA:
      • tranche table;
      • average cost per coin;
      • idle cash;
      • D1 decomposition;
      • H3-D counts against their minimums.
    • TrendCash’s card is visually separate and carries the label control (failed validation gate).
    • The headline line lists DCA only, and names the control as excluded.
  • Live trade list per arm.
  • Veto log: LLM rationale (≤300 chars), decision, and shadow outcome once known. For DCA, the outcome is the decision’s deferral outcome ρ_ij at the window end. The rationale is model-controlled text that headlines can steer, so the page HTML-escapes it and never renders it as markup.
  • LLM cost, budget burn and latency panel.
  • Frozen whitelist, labelled “Passed the Conservative screening profile as of ”, with sources and snapshot hash. Never “halal coins”.
  • Run metadata:
    • run ID, T0;
    • model ID, served provider, served model;
    • reasoning effort, max_tokens;
    • request config hash and prompt hash, both per strategy;
    • strategy commit SHA and config hash;
    • PRD commit SHA and verdict-script commit SHA;
    • DCA’s k and s.
  • Run history: every started run, including voided and abandoned runs, with T0, config hash, end reason and verdict. It stays on the page permanently, and the readout cites it in full.

Freshness: data no older than 60 seconds, auto-refresh.

Disclaimers (always visible):

  • “Paper trading with simulated money.”
  • “Not investment advice.”
  • “Screening status reflects the cited sources under the Conservative profile, not a fatwa or certification.”
  • “Quote currency is USDT, whose permissibility is itself disputed.”

Banned content:

  • Buy or sell calls, “signals” language, and profit projections. The DCA tranche table describes past paper fills (“paper tranche filled”, “skipped by the gate”), never an instruction to buy.
  • Any link that places a trade.

Architecture:

  • freqtrade’s REST API has a single full-control user and no read-only role [FREQTRADE-REST-API-01]. Each bot’s API therefore listens only on an internal docker network and is never published beyond the host’s loopback interface (the template maps [private address] for the owner’s SSH tunnel and nothing else).
  • The collector reads through a GET-only allowlist proxy on that network, or reads each bot’s trades SQLite through a read-only mount. It writes to its own SQLite and serves a small JSON API.
  • The full-control credentials never leave the bot host’s private network.
  • A static dashboard page consumes the collector’s API.

8. Environments

WhereWhat
Build VPS (this one)Build the pack; run contract evals, behavioural LLM evals, unit tests and backtests. Option 3 research (pre-registering and gating new families, pack issues #37 and #38, and the BinHV45_Regime_5m case study, pack issue #43) runs here. No experiment deploy.
Lab VPS (dedicated, separate from the build VPS and from production; chosen 2026-10-07, pack issue #17)Runs the arms, the collector and the dashboard behind Traefik. labs.buildeon.solutions DNS points here (since 2026-10-07).

Lab VPS resources (amendment 1):

  • 4 freqtrade bots: DCA A, DCA B, TrendCash A and TrendCash B.
  • 4 GET-only proxies, one per bot, or a read-only SQLite mount per bot.
  • Networks: each bot gets its own egress network, as in the M2 smoke template. The two B arms share one TypeSafe key, TYPESAFE_API_KEY (one OpenRouter key before the 2026-10-21 amendment).
  • One collector for both strategy families, plus the static dashboard behind Traefik.
  • Sizing: size the host from the M2 smoke run’s measured peak memory, CPU and disk for all of these together, and record the figures in the deploy repo.

Deploy config for the lab VPS follows the source/deploy split. Source (strategies, collector, dashboard, verdict script) lives in this repo under experiments/llm-gate-ab/, and the gate mixin is skill 06’s strategies/llm_gate/; deploy compose lives in the deploy repo.

9. Milestones

IDMilestoneExit check
M0Pack skills 00–06 built; contract and behavioural evals passCI green; behavioural eval report committed
M1Strategies validated and frozen; model and provider picked and pinned; prompts frozen; verdict script frozenSee the M1 checklist below
M2Lab VPS smoke run: all four bots dry-run for 72h, arm B on the pinned decision backend (a fresh run after the 2026-10-21 amendment)Collector freshness ≤60s; journal rows validate; no duplicate decisions; burn projection ≤60% of each sub-cap; dashboard renders both families; the run spans at least one DCA tranche time (the smoke’s own T0 plus 7j days, at 12:00 UTC on any weekday), so a DCA tranche batch is observed in both DCA arms
M3T0 → T_endWeekly snapshot JSON committed to the results branch
M4Readout published on the labs page, whatever the outcomeThe frozen M1 verdict script, run unchanged, produces the verdicts. No analysis choice is made after T0.

M1 checklist:

  • At least 2 live screening adapters, and a non-empty whitelist that contains every pair each strategy needs.
  • DCA’s strategies/dca/VALIDATION.md is committed with every baseline-family check passing (§5).
  • TrendCash’s control re-run under gate 0.4.0 (§5, TrendCash control): a new counted ledger attempt with --control-label, dry_run_wallet 10,000 and capital.headroom 0.50 recorded in a dated PREREG.md addendum first, run on the merged pack (attempt 3, erratum 2026-10-08, §14; attempt 2 used 5,000 and 2,250). Its report and ledger row are committed, with verdict known_weak_control_paper_only, check 1 pass and setup.stake_amount 2,500.
  • The verdict script includes the DCA-family rules (§13, item a) and is committed with unit tests on synthetic journals:
    • signal family: base-rate, sizing-only, budget-cap truncation, unmatched-veto and each void case;
    • DCA family: always defer (p_better = p_worse = 1, H3-D undecided), a random skipper (pass and fail rates each within Monte Carlo error of 5% over many synthetic paths, never asserted as Δ3-D ≈ 0 on one path), a skilled skipper, all-fallback, a delisting, a missed tranche, a fallback after skips, and the evaluability edges.
  • The verdict-script SHA, PRD SHA and T0 config hash are recorded.

Frozen at M1, and why amendment 1 is legitimate. The verdict script stays frozen at M1, and M4 runs it unchanged. Amendment 1 is dated 2026-10-07, before M1 and before T0: no run has started, and no verdict-script SHA has been recorded. The DCA rules therefore enter the script before it is frozen, and none is chosen after any experiment data exists. Any later change needs a further dated amendment before T0. After T0 nothing changes.

10. Non-goals

  • Real money. Going live is a separate decision under skill 07, after M4.
  • LLM-originated trades, leverage, margin, futures, shorting, options, or earn/yield products.
  • Multiple LLM arms or model bake-offs (possible v2).
  • Selling or marketing anything.

11. Risks

RiskMitigation
Model deprecated or provider outage mid-runModel and provider pinned. Fallbacks are counted and shown. The void thresholds in §3 apply.
Slot effects in both directionsH1 is the full-portfolio return by definition. Slot-freed and slot-blocked trades are reported as diagnostics.
Size shrinkage passing as skill“Agentic better” needs a positive selection effect (signal family) or a passing H3-D randomization test against B’s own decisions on random dates (DCA). B_vo, B_vo-D and exposure-adjusted return are always shown.
Deferring in a falling market passing as skill (DCA)H3-D compares B’s deferral outcomes with the same decisions placed on random dates, so a path-independent pattern passes at most at α = 0.5% (a constant one never); a past-price rule without predictive power can pass more often, so H3-D’s α is 0.005, 12 past-price rules are pre-registered beside B, and “LLM beyond simple rules” against one primary rule fixed in advance (skip above the 28-day MA) is reported as a second finding; the D1 decomposition shows the market term separately
Market drift over the deferral weeks read as judgement (DCA)Carry-forward keeps every withheld amount in the plan, so it misses about a week of drift per decision, not the rest of the window; the decomposition separates the market term from the skill term on the dashboard and in the readout
Exchange minimum shrinks the DCA basketThe basket rule is pre-registered (§5). At 10,000 USDT per arm it gives k = 10 with about 128% to spare (erratum 2026-10-08, §14; about 14% at 5,000); a larger minimum on the T0 − 1 day run lowers k by the same rule, never the rule itself
Re-asking the LLM until it approvesOne cached decision per signal or tranche; duplicates void the run
Screening list changes mid-runWhitelist frozen at T0. Delistings are handled identically by every arm’s delist filter and by the baselines.
Prompt injection via headlinesHeadlines go into a delimited data block (the System One state’s untrusted_headlines) after a local pattern filter. Output is validated. B can only approve or skip. Adversarial text can still move the decision model’s verdict, a disclosed limitation (§5, LLM inputs). With the typesafe backend there is no injection 403, so only the local pattern filter applies. With the OpenRouter backend, an injection block on a DCA tranche makes B retry it without headlines, then buy as A does if it still fails, never skip, so one hostile headline cannot void the run.
Dashboard read as adviceFixed disclaimers, banned-language lint in CI, no per-coin “buy” framing, whitelist never labelled “halal”
Control’s verdict quoted as a headlineTrendCash carries its label everywhere and is excluded from n, k and the headline line by the verdict script
Restarting until a run looks goodEvery started run stays in the public run history; the readout cites all of them

12. Open questions

  1. Lab VPS host and region. Resolved: the dedicated lab VPS (§8). The region question was about exchange reachability: the unscored pilot there has polled Binance public spot data since 2026-10-07, and the M1 headline feeds load from it.
  2. Does the labs hub live in buildeon-web or as its own app? Resolved: its own app, served at labs.buildeon.solutions from the lab VPS.
  3. RSS feed list for headlines. Resolved 2026-10-08 (owner, pack issue #65): strategies/llm_gate/rss-feeds.json (§5, LLM inputs (arm B)).
  4. Which OpenRouter provider slug to pin. Resolved 2026-10-08 (owner, pack issue #65): nvidia (§5, LLM provider and budget).
  5. DCA basket size against the exchange minimum. Resolved: the paper wallet is 10,000 USDT per arm, in every arm (5,000 on 2026-10-07, then 10,000 on 2026-10-08, §14), with the ×0.25–1.0 shrink kept. The basket rule gives k = 10, s = 75.38 and 130 tranches (§5, DCA mechanics). At 1,000 USDT it gave k = 2.

13. Implementation notes (amendment 1)

All four items have landed (2026-10-08): (a) pack PRs #49 and #60, (b) pack PR #50, (c) pack PR #52, (d) pack PRs #56 and #63. The verdict script is frozen with them at M1 (pack issue #65).

  • (a) DCA metrics and verdict rules in experiments/llm-gate-ab/ (pack issue #40).
    • Config:
      • a per-strategy family (signal or dca);
      • the exact control label string;
      • DCA’s k, s and tranche times;
      • per-strategy prompt and request-config pins;
      • run.fee_rate 0.001 and run.slippage_rate 0.0005, which are the config defaults (pack PR #49; they were 0.0025 and 0).
    • Matching: arm-A fills to arm-B tranche decisions by (pair, τ_j), with the unmatched and missed counts.
    • Metric functions:
      • final value;
      • average cost per coin;
      • idle cash (series, time-weighted mean, end value);
      • the carry-forward budget recursion b_ij, checked against both arms’ orders;
      • r_ij, ρ_ij (with r = 0 for a missing next tranche), w_ij, ω_ij, W_m and W_all, n_m, D_m, ρ̄_A, ρ̄_W, Δ3-D, the H3-D randomization test (§3) and the hit rates, with x_ij as the ordered stake and costs with the entry fee on top (§3, Fee convention);
      • H3-D’s pins: α = 0.005, random.Random(stats.SEED + 4) per strategy verdict, the written-out Fisher–Yates loop, W_m* = 0 as a tie, the identity replay check that stops the script, m = x / b, and B-missed or row-less cells as unmatched with m = 0;
      • the 12 rule benchmarks (§3, Pre-registered rule benchmarks) in their own module in FROZEN_FILES: replays at arm A’s fills with the same carry-forward, each rule’s Δ3-D, D1 and D1(B) − D1(R) as point estimates, and the “LLM beyond simple rules” bootstrap against rule 1 only (stats.SEED + 5, outcomes yes / no / rule better, one-sided α 0.05 for yes and 0.01 for rule better); rule 6’s u from stats.SEED + 6;
      • the evaluability rules;
      • the D1 decomposition, including the residual;
      • B_vo-D;
      • the delisting valuation for arms;
      • the DCA baseline with the strategy’s s;
      • the arm A vs baseline cross-check;
      • capital-skip counts from the bot logs.
    • Verdict: H1-D, H2-D and H3-D, the DCA verdict order, the separately labelled second finding beside the headline, decomposition reason strings, the DCA void-condition-2 rate-only rule, a DCA fallback counted as buy at 1.0, and the headline that excludes the control.
    • Every new module goes into FROZEN_FILES. Synthetic-dataset tests cover the M1 checklist.
  • (b) DCA strategy and LLM gate on tranches (pack issue #36; amendment 1’s requirements, the owner decisions’ updates and the pack PR #42 review fixes are in three comments there).
    • Schedule and execution window.
    • Basket rule script (k, s, M_k), run at T0 − 1 day.
    • No exits, no protections.
    • Carry-forward: the budget recursion, an ungated final tranche that invests everything remaining, a fallback that deploys the whole budget, and the dca_tranche row’s budget fields.
    • Fallback buys the whole budget b_ij at 1.0, applied by the hooks themselves: custom_stake_amount for a coin’s opening tranche and adjust_trade_position for later ones, each catching every exception.
    • Retries inside the 24 h window before a fallback counts, journaled on the tranche’s single row.
    • The delisting flag decided at τ_j and recorded by both arms.
    • dca_tranche schema side, with a negative fixture.
    • Sequential, independent per-coin calls with the portfolio as of τ_j.
    • DCA prompt template.
    • strategies/dca/VALIDATION.md with the eight mechanical checks.
  • (c) Dashboard panels for DCA (pack issue #41).
    • Per-family panels; the tranche table with escaped rationales.
    • Average cost per coin; the idle-cash chart; the D1 decomposition; H3-D counts against minimums.
    • The control card and label; the headline line excluding the control.
    • Banned-language lint covering the DCA copy.
  • (d) Owner items before M1, no issue.
    • TrendCash’s --control-label re-run under gate 0.4.0, a new counted ledger attempt with the 10,000 USDT wallet and headroom 0.50 (attempt 3; §5, TrendCash control; M1 checklist; erratum 2026-10-08, §14; attempt 2 used the 5,000 USDT wallet).
    • Committing the 2026-05 backtest report (§2).

14. Changelog

  • 2026-10-21, arm B’s decision source becomes TypeSafe Jev with a binary approve or skip, and T0 moves to Monday 2026-10-26 12:00 UTC (pack issue #77), before T0. No data existed. The pinned free OpenRouter model, nvidia/nemotron-3-super-120b-a12b:free, was retired: every call in the 2026-10-20 smoke returned HTTP 404. The owner will not pay for an LLM, and a local 3B model on the 4 vCPU lab VPS took about 105 s to process a gate-sized prompt, against the pinned 20 s timeout. Nothing had run as the real experiment: no arm traded, no journal row was written, and no data of any kind exists. The owner decided the following (2026-10-21).
    • New decision source. Arm B’s model is TypeSafe Jev, jev-1.13.0, a hosted decision-only model called through the System One API [TYPESAFE-SYSTEMONE-01], at USD 0.042 per million input tokens (the owner’s price) with output free. The gate’s new LLM_GATE_BACKEND=typesafe mode sends the same inputs the prompts carried as the request’s state, with one pinned choice question per strategy (strategies/llm_gate/prompts/gate-jev-v1.json, dca-jev-v1.json) whose criteria are approve and skip. Live text in §1, §5 (Arms, Hard rules for arm B, DCA tranche gating, LLM inputs, LLM provider and budget), §8, §9 (M2) and §11 is updated; skill 06 and experiments/llm-gate-ab/freeze.py pin backend, model, question hashes and request config hash.
    • Binary decisions. Each decision is approve or skip. Approve is ×1.0 (the strategy’s own size on an entry, the whole budget b_ij on a DCA tranche, the exit allowed on an exit) and skip is the schema’s reject (×0 on a DCA tranche, carried as before). Shrinking is no longer offered. This matches the twelve pre-registered rule benchmarks, which already decide skip (m = 0) or approve (m = 1.0) (§3). The formulas that allow m in [0.25, 1.0], B_vo and B_vo-D, the basket rule’s ×0.25 minimum-stake test and the startup check of 0.25 × the stake against the exchange minimum are kept as written; they still hold, and the last two are now merely conservative.
    • The hypothesis changes in what it tests. The question was whether an LLM’s timing beats the schedule. It is now whether a decision-only model’s timing beats the schedule (§1). A result says nothing about general-purpose LLMs. The labels “LLM-gated”, “LLM cost term” and “LLM beyond simple rules” are kept, because they are dashboard labels and strings in the frozen verdict script’s output, and refer to this model.
    • Journal. On a typesafe judgement rationale is null, raw_output holds the answer (choice, confidence, probabilities), provider is typesafe, served_model comes from the response and must be jev-1.13.0, tokens come from usage, and cost_usd = input tokens × 0.042 / 10^6. The gate-decision schema allows a null rationale on a real judgement only when provider is typesafe. The fallback causes, the fallback actions (reject on an entry, approve on an exit, buy the whole budget on a DCA tranche, m = 1.0) and the 20 s timeout are unchanged; 429 and timeouts follow the existing retry rules.
    • Budget. The owner’s separate USD 5 TypeSafe credit replaces the OpenRouter USD 25 cap: USD 2.00 per strategy at T0 (USD 4.00 of it allocated; owner decision 2026-10-22, pack issue #82, superseding the USD 2.50 first stated here). The 50% alert, the cap rule (truncate at or after day 75, void before) and the 60% burn-rate check before T0 are kept, the last applied to the real per-call cost.
    • New dates. T0 = Monday 2026-10-26 12:00:00 UTC; T_end = T0 + 90 days = 2027-01-24 12:00 UTC; tranche j is due at T0 + 7j days, so the last (j = 12, day 84) is 2027-01-18 12:00 UTC. The T-1 day basket and ranking run is at 2026-10-25 12:00 UTC. A fresh 72 h M2 smoke on the new backend comes first; if it slips, T0 may move one weekday later by a further dated entry.
    • Disclosed limitations. Arm B depends on one hosted service, so an outage is a fallback that counts against the void thresholds. Adversarial text in the state can move the model’s verdict; the headline pre-filter and the fallback rules are kept unchanged against it, but a hostile headline the filter misses could still tilt a decision. The model gives no rationale, so a decision is audited only through its inputs, choice and probabilities.
    • What does not change. No threshold, seed, void condition or verdict rule changes, and no verdict logic changes; the verdict manifest hash changes because it includes the gate files edited here. The hour and weekday rules for T0 are kept. Earlier entries below that name the Nemotron model, the USD 25 cap or the earlier T0 dates are left as written and are superseded by this one.
    • Review follow-up (2026-10-21, pack issue #79). Two sentences are corrected: “What does not change” above said the verdict script was untouched, but verdict.FROZEN_FILES includes the gate client, gate and decision schema edited here; and the §11 injection risk row is scoped to the OpenRouter backend. A typesafe answer whose choice is not a highest-probability option is now invalid_output. The T0 freeze refuses a collector.json whose run.pins model, provider and served_model are not jev-1.13.0, typesafe and jev-1.13.0, or whose run.metadata sets max_tokens or reasoning_effort, because the collector compares every journal row with those pins and a changed pinned item voids the run (§3, Void conditions, 5).
    • Budget change (2026-10-22, pack issue #82), before T0, no data existed. The per-strategy cap is USD 2.00, not USD 2.50, so USD 1.00 of the owner’s USD 5 TypeSafe credit stays unallocated. Only the cap changes: the 50% alert, the cap rule (truncate at or after day 75, void before) and the 60% burn-rate check before T0 apply to the new cap. No threshold, seed, void condition or verdict rule changes.
  • 2026-10-19, T0 moves to Friday 2026-10-23 12:00 UTC; the Monday-only rule is dropped (pack issue #75), before T0. No data existed. The planned start, Monday 2026-10-19 12:00 UTC, was missed: the lab was still on smoke inputs and the deploy runbook’s reset cutoff (T0 − 15 min, a deploy rule, not defined in this PRD) passed. Nothing ran as the real experiment, so no arm traded, no journal row was written, and no data of any kind exists. The owner set a new T0 (2026-10-19).
    • New dates. T0 = Friday 2026-10-23 12:00:00 UTC; T_end = T0 + 90 days = 2027-01-21 12:00 UTC; tranche j is due at T0 + 7j days, so the last (j = 12, day 84) is 2027-01-15 12:00 UTC. The T-1 day basket and ranking run is at 2026-10-22 12:00 UTC.
    • Rule change. T0 may fall on any weekday (Monday to Friday, enforced), and must still be exactly 12:00:00 UTC (whole second, UTC). The Monday-only requirement is removed from llmgate_ab/config.py and strategies/dca/dca_core.py. Live text in §3, §5 (DCA mechanics, Schedule, Why this time), H3-D and M2 is updated; the weekday rationale (a weekday, mid-session, away from the 00:00 UTC rollover) is kept as written and holds for a Friday. M2’s tranche-batch check now needs the run to span a DCA tranche time of the smoke’s own T0 + 7j days rather than a Monday.
    • What does not change. No threshold, seed, void condition, hypothesis or verdict rule changes, and the hour rule is kept. The verdict script is untouched. H3-D’s reasoning about day-of-week rules still holds, because every τ_j falls on one fixed weekday. Earlier entries below that state Monday 2026-10-19 are left as written and are superseded by this one for T0.
    • Superseded record. The T0 freeze record for 2026-10-19 (pack PR #74) is for the missed start and is not used.
  • 2026-10-09, run-start gate (pack issue #65), before M1 and T0. strategies/trendcash/TrendCash.py is pinned in PREREG.md and has no T0 guard, and the bots start before T0, so TrendCash could otherwise trade before the window opens.
    • The gate. Before each TrendCash arm first starts, the deploy writes one freqtrade global pair lock into that arm’s fresh database: pair *, side *, reason “experiment run starts at T0”, ending exactly at T0. Both TrendCash arms get the same lock. It lapses at T0 by freqtrade’s own clock, and nothing removes it by hand.
    • It is part of each arm’s initial state. It is pre-registered here, written before each arm’s first start and before T0, identically in both arms, and nothing writes to the databases after that. It is the arms’ starting state, not an intervention in a running arm, so condition 4 (§3), whose text is unchanged, does not apply to it.
    • DCA needs no lock. Its schedule already starts at T0 (τ_0 = T0), so it places no order before T0.
    • Check after T0. The deploy checks after T0 that no arm traded and no arm-B journal row was written before T0.
    • Readout guard. The verdict script checks the same thing, in both families. The metrics kept an arm’s trades and B’s journal rows up to the window end with no lower bound, so a trade or row before T0 would have been scored silently. Now a trade opened before T0, in either arm, or an arm-B journal row whose ts is before T0, voids the run with the reason “trading before T0: ”, which lists each trade (arm, trade id, pair, open time) or row (decision id, time), up to 20. A trade opened or a row written exactly at T0 is allowed. It is a Void, not Unavailable, because the data is complete and shows that the pre-registered start was not honoured.
    • What does not change. This adds a void path and loosens nothing: no hypothesis, threshold, seed or other void condition changes. The guard is in analysis/metrics.py and analysis/verdict.py, which are in the verdict script’s frozen set, and a Void at T_end + 7 days keeps the reason (analysis/readout.py). The verdict script’s SHA-256 changes and is recorded at T0.
  • 2026-10-08, owner decisions at M1 (pack issue #65), before T0. T0 is fixed at Monday 2026-10-19 12:00 UTC (§3). The arm-B headline feeds are pre-registered in strategies/llm_gate/rss-feeds.json: CoinDesk, Cointelegraph, The Block and Decrypt. All four parsed through the gate’s own headline code from this host on 2026-10-08, with 9 to 20 fresh items each; Bitcoin Magazine and CryptoSlate were rejected (HTTP 403). The provider slug is pinned to nvidia (§5, LLM provider and budget). §12 questions 1 to 4 are resolved, and §8 records the chosen lab VPS. No threshold, seed, void condition or verdict rule changes. The same day, the editorial entry below was completed for two more stale sentences: the pre-fee baseline mode and the config fee defaults, both landed in pack PR #49.
  • 2026-10-08, editorial (pack issue #65), before M1 and T0. No rule changes. Passages outside this changelog that still stated superseded values or pending work now state the current ones: the amendment 1 banner (10,000 USDT wallet; sizing parity with TrendCash’s 0.50 headroom), §5 TrendCash control (attempt 3 is done, its result meets the rule, and attempt 2 is history), §12 question 5, and §13 (all four items landed). The attempt-3 result is added to the pack issue #58 entry below, as that entry promised. Earlier entries are left as written.
  • 2026-10-08, erratum (pack issue #58), before M1 and T0: a 10,000 USDT wallet in every arm, and TrendCash re-sized. The owner moved the paper wallet from 5,000 to 10,000 USDT for all four arms (pack issue #58, 2026-10-08). Separately, TrendCash pre-registers capital.headroom 0.50 so capital rarely binds, and its control status is re-recorded by gate attempt 3. Every absolute USDT figure in this PRD was re-checked; the body now shows the new value with the old one beside it. The earlier entries below are left as they were written.
    • DCA, recomputed with the unchanged basket rule (§5, DCA mechanics):
      • D = 0.98 × 10,000 = 9,800 USDT, 100 USDT under the 0.99 tradable ratio’s 9,900;
      • s = s_10 = floor_cents(9,800 / 130) = floor_cents(75.3846) = 75.38 USDT (was 37.69);
      • minimum-stake check: 0.25 × 75.38 = 18.845 ≥ 1.10 × (5.00 × 1.5) = 8.25 USDT, about 128% to spare (was 9.4225 ≥ 8.25, about 14%). k stays 10, so 130 tranches and 120 gated decisions;
      • largest carried order: 13 × s = 13 × 75.38 = 979.94 USDT (was 489.97);
      • Hold beside DCA invests 13ks = 130 × 75.38 = 9,799.40 USDT (was 4,899.70);
      • common idle cash: W0 − 13ks(1 + f) = 10,000 − 9,799.40 × 1.001 = 10,000 − 9,809.1994 = 190.8006 ≈ 190.80 USDT (was ≈ 95.40, erratum 2026-10-07 (b)).
      • strategies/dca/DCA.py and DCALLM.py read the wallet and s from their configs and hold no wallet constant, so their pinned sha256 do not change and no re-pin is needed. The configs, tests, fixtures and the collector’s DCA_START_CAPITAL move to 10,000 and 75.38.
    • TrendCash (§5, Capital and fees; TrendCash control): capital.headroom 0.50 (tighten-only in gate 0.4.0) with max_open_trades 2, so the stake is floor(10,000 × 0.50 / 2) = 2,500 USDT, the volatility-scaled stake 1,250 to 2,500, and arm B’s smallest order 0.25 × 1,250 = 312.50 USDT. freqtrade refuses a second slot only after a realised loss of about 49.5% of the wallet, 1 − 2 × 2,500 / 9,900, against about 9% at 0.90. The value was chosen from attempt 2’s drawdown and skip data, not its returns, and is justified in strategies/trendcash/PREREG.md (attempt 3 addendum). At 0.90 the 10,000 USDT wallet alone would have given 4,500 and the same 9% tolerance, because the stake scales with the wallet.
    • Why TrendCash was re-sized. Attempt 2 (5,000 wallet, headroom 0.90, stake 2,250) refused about 50% of holdout entry attempts and 12.5 to 18.3% per check for lack of capital. Arm B’s vetoes change its free balance, so the skips would differ between arms and confound the control A/B.
    • TrendCash attempts: 1 (2026-10-07, gate 0.2.0, 1,000 wallet, stake 495): fail, eligibility none, check 1 pass. 2 (2026-10-08, gate 0.4.0, 5,000 wallet, headroom 0.90, stake 2,250, --control-label): known_weak_control_paper_only, capital skips 12.5 to 18.3% per check. 3 (2026-10-08, gate 0.4.0, 10,000 wallet, headroom 0.50, stake 2,500, --control-label): run once after this erratum; known_weak_control_paper_only, check 1 pass, zero capital skips, holdout −1.24% over 40 trades against Hold +27.45% (strategies/trendcash/RESULTS.md).
    • The power sketch does not need a re-run. power_sim.py (and block_check.py, which imports it) uses s and W0 only as s / W0: orders are multiples of s, equity is divided by W0 for D1 and the drawdowns, and evaluability counts withheld amounts in tranche units. s / W0 is 37.69 / 5,000 = 75.38 / 10,000 = 0.007538 exactly, so every figure is unchanged. The constants in power_sim.py and its README now read 75.38 and 10,000; a reduced run of power_sim.py (60 runs, 300 draws) and block_check.py (30 runs, 300 draws), same seed, before and after the change gave identical output apart from the runtime line. results.md and block_check.md stand as committed.
    • What does not change: the basket rule, the ×0.25–1.0 shrink, k = 10, the schedule, the fee and the verdict rules. R, V and the D1 decomposition divide by W0, so every pre-registered threshold in percent or pp is unchanged.
  • 2026-10-08, erratum (pack issue #55, pack PR #56), before M1 and T0. The TrendCash control re-run (attempt 2, gate 0.4.0, 5,000 USDT wallet, stake 2,250) returned known_weak_control_paper_only, but its capital skips were about 50% of holdout entry attempts (31 refused against 31 trades) and 12.5 to 18.3% per check. §5’s statement that the stake change “touches no signal” was wrong and is removed, and its holdout count is now 36 in attempt 1 and 31 in attempt 2. Arm B’s vetoes change its free balance, so skips are not identical across arms. The control arms are to be re-sized under pack issue #58 (gate attempt 3); §5 and the sizing-parity lines in earlier entries are unchanged until it lands.
  • 2026-10-07, amendment 1 (pack issue #35), before M1 and T0.
    • Strategies: DCA is the primary strategy, and TrendCash is a labelled control. BTCETHGrid_v3 is dropped. The strategy set is frozen, and new families go to a later experiment only (Option 3).
    • New DCA family rules:
      • the hypotheses H1-D, H2-D and H3-D, with their verdict rule and the D1 decomposition (H3-D’s test was replaced in the review fixes below);
      • the minimum skip counts;
      • a stated power analysis;
      • the mechanics: schedule (T0 is a Monday 12:00 UTC), basket and stake rule, never sells, delisting, and execution window (the draft’s no-carry-forward rule is replaced below);
      • the fallback rule, buy at 1.0;
      • the DCA void-condition-2 rule (rate only);
      • its metrics and dashboard panels.
    • Signal-family rules are unchanged, except that the fixed stake gains headroom because of the skipped-entry finding in pack issue #33. Sizing parity with gate 0.4.0: floor(dry_run_wallet × capital.headroom / max_open_trades) with headroom 0.90, the same formula and headroom the gate sizes with, and the arm runs the stake its gate report shows (§5, Capital and fees). This replaces the earlier draft’s floor(0.8 × 990 / max_open_trades). DCA sizing is its own formula, and capital.headroom does not apply to it.
    • Shared changes:
      • the fee is pinned at 0.001 in all bots;
      • baseline costs are pinned at 0.001 fee and 0.0005 slippage per side;
      • prompt and request-config pins are per strategy;
      • the budget is split USD 12.50 per strategy;
      • lab resources are 4 bots, 4 proxies and 1 collector;
      • M1 and M2 exit checks are extended.
    • PACK_SPEC Principle 3 gains the baseline-family exception.
  • 2026-10-07, amendment 1, owner decisions on the draft (pack PR #42), before M1 and T0.
    • Paper wallet 5,000 USDT per arm, in all four bots (was 1,000). R, V and the D1 decomposition divide by W0 = 5,000. The DCA budget is D = 0.98 × W0 = 4,900, so s_10 = 37.69 and the basket rule gives k = 10 (130 tranches, 120 of them LLM decisions); §12 question 5 is resolved. TrendCash’s stake becomes floor(5,000 × 0.90 / 2) = 2,250. The ×0.25–1.0 shrink is kept.
    • Carry-forward replaces no-carry-forward. Unspent budget rolls into the coin’s next tranche in both arms; the final tranche (j = 12) invests everything remaining, ungated, so there are 12k LLM decisions; a buy at ×1.0, including a fallback, deploys the carried amount. Re-derived: the per-tranche bound s ≤ b_ij ≤ (j + 1) × s; H1-D now measures timing, and the draft’s “withholding loses by construction in a rising market” disclosure is replaced (§3); H3-D compares each withheld amount’s one-week deferral outcome ρ_ij = r_ij − r_i,j+1 with all decisions’, with a withheld hit-rate condition; the D1 decomposition’s cash term becomes a market term in ρ; B_vo-D, the validation checks, the prompt, the schema row and the power sketch follow.
    • TrendCash control re-run approved: under gate 0.4.0 with --control-label and the 5,000 USDT wallet, as a new counted ledger attempt at M1; added to the M1 checklist.
    • Sizing parity with gate 0.4.0: a signal-family arm’s stake is floor(dry_run_wallet × capital.headroom / max_open_trades) with headroom 0.90, the formula and headroom the gate sizes with, and it must equal the setup.stake_amount of the gate report run with the arm’s wallet (§5, Capital and fees). DCA’s tranche formula is its own; capital.headroom does not apply to it, and skill 04 says so.
    • BinHV45_Regime_5m case study (pack issue #43) listed under Option 3 as a candidate for a later experiment only; it is not part of this one.
  • 2026-10-07, amendment 1, review fixes (pack PR #42), before M1 and T0.
    • DCA hook exceptions: both DCA hooks catch every exception and apply the fallback themselves (buy b_ij at ×1.0, journal gate_error), because freqtrade 2026.9’s defaults would buy the config stake (custom_stake_amount) or place no order silently (adjust_trade_position); validation check 7 tests a raising hook on both paths (§5, DCA tranche gating).
    • DCA counts and the D1 decomposition: each count is marked matched (n_m, W_m, D_m) or all (W_all, the unmatched share); the market and skill terms use W_m, so they sum exactly to the matched part of D1, and the residual lists unmatched decisions, one-arm misses and truncation. The fee convention is pinned (pre-fee stake, fee on top, so the identity carries 1 + f), idle USDT is computed by the collector, x_ij is the ordered stake, lot-step truncation residue is not carried, and check 2 tests the general recursion with a lot-step tolerance and max_entry_position_adjustment. Hold beside DCA invests 13ks, and both DCA baselines use freqtrade’s fee convention.
    • DCA retries: a transient failure (timeout, 429, 5xx, served_model_mismatch, prompt_injection_blocked) is retried up to four times inside the tranche’s 24 h window with the τ_j inputs, and after a 403 without the headline block, so one outage or one hostile headline cannot void the run; each retry is journaled on the tranche’s single row.
    • DCA delisting is decided at τ_j from what the bot can see then (no active market, or no hourly candle in the previous 48 h), irrevocably, and the collector uses the recorded flags.
    • Wording: the outage, H2-D spend and “never enlarge” statements are corrected for carried budgets and one-arm misses.
    • TrendCash: a dated addendum in strategies/trendcash/PREREG.md records the 5,000 USDT wallet and 2,250 stake for the control re-run, and skill 05 states the 5,000 USDT stake floor.
    • H3-D’s test is a randomization test conditioned on the realised prices: 10,000 permutations of the 12 gated dates (seed 20261001), B’s own decisions moved with their dates, carry-forward replayed, one-sided p-values with ties against B, α = 0.05 per side, final tranche excluded. It replaces the 12-cluster bootstrap, under which “always defer” passed in about 11% of runs; constant policies now get p = 1 by construction. The withheld hit rate becomes a diagnostic.
    • Power restated from the committed sketch experiments/llm-gate-ab/power/ (script, README, results.md): the joint probability of “Agentic better” with not-evaluable runs included, about 60% at a true Δ3-D of −7.4 pp and 76% at −9.3 pp, replacing “80% at −8 pp”, which was H3-D alone (restated again at α = 0.005 in the statistical re-review fixes below). Not evaluable is 15.2% (exact binomial), not 13%.
  • 2026-10-07, amendment 1, statistical re-review fixes (pack PR #42), before M1 and T0.
    • H3-D’s guarantee is restated. The whole-date permutation keeps pass and fail rates at most α only for policies independent of the price path. Arm B reacts to past prices, and a permutation that moves a decision onto an earlier date hands the null look-ahead, so in a zero-drift random walk “skip a coin while above its 28-day MA” passed H3-D in 27.8% of runs at α = 0.05 and its mirror failed in 30.2% (the re-review’s runs; up to 24.3% and 28.7% across the 12 rules in the committed sketch). The claim that every no-information policy reads “Agentic better” in at most about 2% is withdrawn.
    • H3-D’s α is 0.005 per side, calibrated in the sketch against the reactive rules: they pass in at most 5.1% and fail in at most 5.4% of zero-drift runs, and read “Agentic better” in at most 1.7%. The skilled gate’s joint power at that level is 51.5% at a true Δ3-D of −7.5 pp and 73.8% at −9.3 pp (59.6% and 76.5% at α = 0.05). These tables replace the earlier ones.
    • Twelve past-price rule benchmarks (six rules and their mirrors) are pre-registered and frozen with the verdict script. Each is replayed on the realised prices with the same carry-forward and reported beside B. A second, separately labelled finding, “LLM beyond simple rules”, holds when the verdict is “Agentic better” and D1(B) − D1(R) has a 90% lower bound above 0 for every rule (an intersection-union test). The headline stays the primary verdict.
    • H3-D pins: its own seed stream (stats.SEED + 4), a written-out Fisher–Yates loop, W_m* = 0 counted as a tie, an identity replay check that stops the script, m from the ordered stake, a cell with no dca_tranche row as m = 0, and a tranche B missed as unmatched (excluded from Δ3-D and the counts, carried as m = 0 in the replay).
    • Disclosed: H3-D credits date selection only, so a gate with per-coin, persistent skill can never read “Agentic better”; and a past-price rule with no predictive power can still pass or fail H3-D more often than α.
    • The power sketch gains a 28-day price warm-up, the 12 rules, the random ramp toward T_end, drift and volatility-regime rows, and the beyond-rules comparison; numpy is pinned in experiments/llm-gate-ab/power/requirements.txt.
  • 2026-10-07, amendment 1, one primary rule benchmark (pack PR #42), before M1 and T0.
    • “LLM beyond simple rules” tests one pre-registered primary benchmark, rule 1 “skip a coin while above its 28-day MA” (not its mirror): yes when the verdict is “Agentic better” and the 90% lower bound of D1(B) − D1(rule 1) is above 0; “rule better” when the upper bound is below 0, whatever the verdict; one-sided α = 0.05 each, H1-D’s level. It replaces the all-12 intersection-union test, which compared B with the best rule chosen after the fact among rules paired with mirrors of opposite-sign D1 (best of 12 about 3.9 pp) and reached 0–0.9% of skilled-gate runs.
    • The other 11 rules are descriptive only: Δ3-D, D1 and D1(B) − D1(R) as point estimates; no rule gets H3-D p-values, a CI, or a label.
    • Power sketch re-run with the same seed and settings (every earlier figure reproduced exactly): the finding now reads yes in 0.2% / 1.2% / 3.5% / 6.9% / 8.5% of skilled-gate runs at a true Δ3-D of −2.1 / −4.3 / −6.1 / −7.5 / −9.3 pp, and in at most 0.3% of no-information runs; 0% by construction when B is rule 1. “Rule better” reads in 4.0–11.1% of no-information runs, above its nominal 5%, and is disclosed as anti-conservative.
  • 2026-10-07, amendment 1, calibrated levels for the second finding (pack PR #42), before M1 and T0.
    • Each named outcome’s one-sided α is calibrated by simulation: the largest of 0.05, 0.025, 0.01 and 0.005 whose worst-case rate over every no-information policy in the sketch is at most 5%. “Yes” stays at α = 0.05 (worst case 0.3%); “rule better” tightens to α = 0.01 (worst case 3.4%, against 11.1% at 0.05). The “anti-conservative” disclosure is withdrawn because the level now holds; the calibration table is in §3, Verdict rule.
    • The bootstrap is unchanged. It understates the spread of D1(B) − D1(rule 1) under no information, and a longer or weekly-matched mean block (1 to 14 days) narrows it further and does not calibrate α = 0.05 (worst case 10.2–19.5%), so the level is tightened rather than the interval changed. The check is experiments/llm-gate-ab/power/block_check.py, output block_check.md.
    • Power sketch re-run with the same seed and settings (every earlier figure reproduced exactly): yes still reads in 0.2% / 1.2% / 3.5% / 6.9% / 8.5% of skilled-gate runs at a true Δ3-D of −2.1 / −4.3 / −6.1 / −7.5 / −9.3 pp; rule better at α = 0.01 reads in 1.1% / 0.6% / 0.3% / 0.2% / 0.1% of them.
  • 2026-10-07, errata (pack PR #49, pack issue #40), before M1 and T0. These corrections were found while the verdict script was implemented. The verdict script is not frozen, so they are still pre-registration.
    • (a) The second finding is null on a Void run (§3, Second finding). The earlier text said “rule better” holds “whatever the verdict”, and Void is one of the five verdicts. A void run is published with its data, but it supports no claim, so the finding is not reported. The verdict script already did this, and this line makes the text match it.
    • (b) The idle cash both arms keep is ≈ 95.40 USDT, not 100 USDT (§6, Idle cash). “100 USDT (W0 − D)” ignored the PRD’s own fee convention (§3, Fee convention): each arm’s tranche fills cost their stake × (1 + f). At k = 10 the arithmetic is 13 × 10 × 37.69 = 4,899.70 USDT of stake; × 1.001 = 4,904.5997 USDT with entry fees; and 5,000 − 4,904.5997 = 95.4003 ≈ 95.40 USDT. In general the figure is W0 − 13ks(1 + f). The bucket also holds a delisted pair’s idle budget, truncation residue, a missed final tranche’s budget and, inside an execution window, the unfilled part of x_ij, so the dashboard calls it “other idle cash”.
    • (c) The second finding is also null when rule 1 cannot be replayed (a cell lacks the 28 days of price history it reads). “No” would assert that B was not beyond the rule, but nothing was compared.
    • (d) Which existing void condition an arm’s order fault falls under. This adds no void condition. An arm cannot buy outside the schedule or sell (§5, DCA tranche gating; Never sells), so these facts from the trades database are condition 4 (manual intervention), and they are still booked in that arm’s V: a buy whose order was placed outside every execution window [τ_j, τ_j + 24 h), the bot’s own window test; a filled sell before T_end, partial or not, open trade or closed; and an arm-A fill that its own schedule did not order. A B fill that disagrees with the journaled ordered stake beyond the per-order tolerance, including a fill on a tranche journaled as a skip and a fill on a cell with no dca_tranche row, is condition 6: the journal no longer describes B’s orders. A ledger .errors.jsonl line (a hook defect) is not a void condition. Its tranche has no row and counts as B-missed with m = 0, as §3 says. If a trade’s orders cannot be read, the verdict script gives no verdict until they can, as for the identity replay check.
  • 2026-10-08, erratum (pack issue #48, pack PR #51), before M1 and T0. The collector took its restart marker from /api/v1/health bot_start_ts. freqtrade 2026.9 sets that once, at the bot’s first start, so the collector recorded no restart after its first poll. That undercounted the restart and downtime ledger read by §3 (conditions 3 and 4) and §6. The fix changes how the ledger is measured, and two of these changes alter rules, so they are listed here rather than called “no rule changes” (§5, Market and timing, has the full method).
    • (a) Restart vs manual intervention. The ledger is each arm’s freqtrade log file [FREQTRADE-WORKER-SOURCE-01], cross-checked against bot_startup_ts [FREQTRADE-HEALTH-SOURCE-01].
      • Changing state to: RUNNING is a process restart. It counts toward downtime (condition 3) and is not voided by itself.
      • Changing state from <OLD> to: <NEW> (/stop, /start, /pause, reload_config) is an operator state change. Inside [T0, T_end] it voids the run automatically (condition 4). Before, it was booked as an ordinary restart and voided only if declared by hand.
      • Limit: the collector cannot tell an operator’s docker restart from one made by the restart policy, so the operator must still declare it. Condition 4 still voids it.
    • (b) Per-restart counting. Each startup line in the log is one restart, so several restarts between two polls are each listed and each charged downtime. Before, the PRD’s “Every restart is logged” would have been met at best once per poll interval. Restarts and state changes are listed only inside [T0, T_end], the window the downtime is clipped to.
    • (c) Downtime vs unobserved time, a definition the PRD did not have before. Downtime is time the bot is shown not to be looping. A restart or state-change episode runs from its last loop (its last RUNNING or PAUSED heartbeat line, or the last loop time a good poll returned) to the startup line after it. A poll during startup or a reload, which has no loop time, now counts as a failed poll, so it can no longer shorten that episode. Unobserved time is time the collector did not poll. It is reported per arm and flagged above 24 h, but it is not downtime.
      • This narrows measured downtime compared with the collector before this erratum. That collector charged a whole collector gap as downtime when the first poll after the gap failed, or when a restart was found after it. Episodes now start at the bot’s last loop that the log or a poll shows inside the gap. A failed-poll episode also now ends at the bot’s startup when that comes before the next good poll.
      • The narrowing applies only where the bot’s own log, or a poll’s loop time, shows it looping. A stall that starts and ends inside a collector gap, with no restart, stays unobserved time and is not counted.
    • (d) A new outcome, Unavailable, which tightens the verdict rule (both families, §3, verdict rules). At the readout, when an arm’s ledger cannot be shown complete over [T0, T_end], no verdict other than Void is published. This applies when the log is unreadable, not read through T_end, rotated past its unread end, shrunk, or silent for 10 minutes while the bot loops; when bot_startup_ts is missing on more than 3 consecutive polls (a reload returns nulls for under a second); or when the marker moves with no log line. Void still comes first. The second finding is null on an Unavailable run.
    • What does not change. The thresholds of condition 3 (24 h, a 6 h difference), condition 4 and the other void conditions stay the same. None of (a) to (d) can turn a run that a pre-registered rule voids into a valid one, and (a), (b) and (d) can only void a run or withhold its verdict. (c) moves only time in which the bot’s log or its /health loop time shows it looping.
    • Frozen set. The collector, metrics and verdict modules are in the verdict script’s frozen set, so its SHA-256 changes with this fix and is recorded at T0. Every arm’s config now needs log_file. A lab pilot database from before the fix shows only first-sight rows, which are not restarts.
  • 2026-10-08, M1 evidence (pack issue #55), before M1 and T0. No rule changes. §2’s owner backtest report is committed as a sanitized copy at docs/evidence/2026-05-fleet-backtests/, with the original’s sha256 recorded in §2. The recount from its table is 16 of 18 runs negative, 5 of 6 in the +48% BTC regime, as §2 already states.
  • 2026-10-08, dashboard readout and blinding (pack issue #41, pack PR #52), before M1 and T0. These are owner decisions on the review of the dashboard. They change no hypothesis, threshold or void condition.
    • The experiment is not blinded. The live A and B equity curves give the running D1 exactly, and B’s public tranche decisions, with public prices, give each skip’s outcome r and ρ, so the point estimate of Δ3-D too. Both follow from §7’s must-haves (equity curves, tranche table, idle cash, H3-D counts against their minimums), which stay. The dashboard says so, and calls nothing “withheld” that the live data reveals.
    • Integrity rests on three things, not on secrecy: the frozen rules (the verdict script and its SHA-256, recorded at T0), the fixed T_end, and void on intervention (§3, condition 4).
    • Formal statistics, p-values and bounds are published only in the frozen readout. Before it, the API serves an allowlist of operational data: no hypothesis value or status, interval, p-value, D1 decomposition, H3-D test field, benchmark or second finding. A void before T_end shows its status and the kind of condition only.
    • The readout is frozen at T_end. After T_end the status is “Computing final readout”, with no readout value, until the final data is in: an ok poll of each arm at or after T_end (the bot log is scanned in the same pass), each pair’s price at the final grid hour (a delisted pair excepted), and every outcome over the whole window (no DCA tranche pending; TrendCash’s shadow ledger built after the window end with no outcome pending). The first readout computed from complete data is stored and served from then on, across restarts. It is never recomputed, so later data cannot change a published verdict.
  • 2026-10-08, erratum (pack issue #59), before M1 and T0. After T_end the readout waits for the final data (entry above, dashboard readout and blinding), and it had no deadline: a dead bot, a missing price, a pending tranche or an unbuilt shadow ledger left the status at “Computing” for ever.
    • Rule (fail closed): if the readout is still not ready at T_end + 7 days (the same list of final data), the strategy’s verdict is Void, reason “readout data incomplete” with what was missing (§3, void condition 7).
    • The outcome does not depend on when a refresh runs. Readiness is decided from the data recorded at or before T_end + 7 days, by its recorded time (a poll’s timestamp, the shadow ledger’s built_at, a price’s first fetch time, a journal row’s first ingest time; §3, void condition 7). The collector writes these times. Data complete at T_end + 6 days gives a normal readout. A price backfilled after the deadline gives Void, even when no refresh ran between the deadline and the backfill. Data recorded before the deadline gives a normal readout, even when the first refresh runs after it.
    • The delisted-pair exception is defined exactly in §3, void condition 7: a delisting flag at τ_j, the 48 h rule on recorded prices, or the pair missing from every arm’s whitelist at its first ok poll at or after T_end. The last covers a pair delisted in the last 48 h of the window. Without it, that pair’s missing final-hour price would void the run.
    • Reason format. A DCA reason ends with the D1 decomposition (§3, Verdict rule). This applies both to a run voided by the deadline and to a run that was already void. An Unavailable run becomes Void, because void outranks unavailable (§3). It keeps its restart-detection reasons before the deadline reason, and the headline counts it instead of reading “Verdict unavailable”.
    • The Void readout is frozen and stored like any other. Later data cannot turn it into a verdict. A run that is already void keeps its own reasons, and the new one is added last. The second finding is null, the headline names the strategy, and the “Computing” reason states the deadline.
    • This only adds a void path. No existing rule is loosened: no hypothesis, threshold or other void condition changes, and a run that had a verdict before still gets the same one when its data is complete within 7 days.
    • Frozen set: the whole void path. What counts as missing (the list above and the delisted-pair exception), the recorded-time cutoff, the deadline and the Void readout are in analysis/readout.py. That file is added to the verdict script’s frozen set. The collector, store and export that write and export the recorded times are already frozen. service.py is not frozen. It only passes the exported dataset, the verdict output and its clock to readout.settle, then serves and stores what comes back. The verdict script’s SHA-256 changes and is recorded at T0.