From 44d806a6d18445b3dc076fffceee6e4e46678b51 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Thu, 13 Aug 2026 01:47:57 -0400 Subject: [PATCH] chore(skills): track keel-asset-scout in the repo, and record today's measurements in it The scout skill lived only in ~/.claude/skills/, outside version control, with a single .bak as its entire history. Most of it is not procedure -- it is recorded reasoning about measurements that cost real compute to produce. An untracked file loses the argument along with the rule. Tracked via `git add -f` because `.gitignore:34` ignores `.claude/`. That is the same exception the two review-fix-merge-pr skills already use, so this follows existing precedent rather than inventing a location. Content added today, all additive -- all 13 original sections are intact, including the guardrails (Do NOT, Rationalizations, the citation-fabrication warning, Output 2's rejection history): - "Taker math controls the field": round-trip friction is ~2.5% of notional, levied on the SEARCH rather than the edge, and the viable intersection is vacant across all 24 assets. Liquidity and admissibility are entry conditions, not evidence. - "What has already been measured": points at the three experiment documents, and separates asset expansion as a refuted PERFORMANCE fix from its still-valid uses (statistical power, the compliance pipeline, future rules) -- which is why the skill is not retired. - Four new required citation fields: `cost_regime`, `sample_size`, `evaluation_window` and `not_already_covered`. Each is anchored to a specific way this project was misled -- a result measured at 5bp that does not transfer to 1.2%, rsi_meanrev's 1.1631 on n=38 collapsing to 0.8396 at the floor, ZEC compressing 92.7% of lifetime PnL into one regime, and entry_lookback=55 sitting inside an already-swept grid. - Fill-model framing: entries fill at the next bar's open as market orders (#258), so a technique assuming a resting limit or stop entry will not execute as described. - Four Rationalizations rows, including "this backtest looks clean, the numbers are all plausible" -- two defects produced plausible, internally consistent output for the life of the project while 2,712 tests passed. Two things deliberately NOT added, with the reasoning recorded so the next proposal to add them can see why: a check on the pending-lifespan invariant (it raises rather than logs, so a violation ends the run and is unobservable from a proposer), and a stationarity rejection threshold (ZEC under pullback_continuation is the only pair in the study with no losing complete year, at 0.875 gross -- any such gate passes that and rejects the one thing that made money gross). Co-Authored-By: Claude Opus 5 (1M context) --- .claude/skills/keel-asset-scout/SKILL.md | 424 +++++++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 .claude/skills/keel-asset-scout/SKILL.md diff --git a/.claude/skills/keel-asset-scout/SKILL.md b/.claude/skills/keel-asset-scout/SKILL.md new file mode 100644 index 00000000..76d78cad --- /dev/null +++ b/.claude/skills/keel-asset-scout/SKILL.md @@ -0,0 +1,424 @@ +--- +name: keel-asset-scout +description: Use when scouting new tradeable assets or trading-strategy ideas for keel — sweeping Coinbase USD spot listings for candidates, re-examining assets scouted in a prior run, or researching new rule parameters/techniques. Triggers on "scout assets", "find candidates", "new strategies for keel", "what should keel add", "keel asset scout". +--- + +# Keel Asset Scout + +## The proposer-never-decider principle + +**This skill produces INPUT to keel's existing, unmodified deterministic gate. It never admits, attests, trades, or writes to keel's databases.** Per the governing design +(`docs/superpowers/specs/2026-07-24-llm-asset-proposer-design.md` §5/§6.4): an LLM may reduce +risk directly, but may only *increase* activity — a new asset, a new rule, a buy — by passing +through a deterministic gate a human built and reviews. **You are a proposer. You are never a +decider.** Every output of this skill is a file for a human to read, run `keel assets propose` / +`keel assets attest` / `keel rules backtest` against, and decide on. If you catch yourself about +to run `keel assets attest`, edit an allowlist, or write to a `keel-*.db`, stop — that is the +human's call, not yours. + +## When to use + +- Operator asks to find new candidate assets, "what's out there we're not trading." +- Operator asks to re-check assets scouted before (has anything changed — liquidity, yield + mechanism, allowlist). +- Operator asks for new strategy/parameter ideas, or "any new research worth backtesting." + +Do NOT use this to actually add an asset or rule — that's `keel assets attest` / `keel rules +backtest` / `keel rules promote`, run by the human, never by this skill. + +## Environment + +- **Deployment** (read allowlist from, write outputs to): `~/keel/`. This is the live trading + deployment — see the `keel-deployment-layout` memory. **Never write anywhere in `~/keel/` + except `~/keel/proposals/`. Never touch `~/keel/*.db` or `~/keel/config*.yaml`.** +- **Dev repo** (read-only, for the exact schema/gate source of truth): + `~/Development/work/CodeGate/keel/`. Never edit files here for this skill — if the schema + ever looks like it's drifted from this document, re-read `keel/proposer.py` and + `keel/compliance/screen.py`, don't guess. + +### Where this file lives + +**This is a PROJECT-scoped skill, and this file is the only copy.** It lives at +`.claude/skills/keel-asset-scout/SKILL.md` in the dev repo and loads only for sessions working in +that repo — which is correct, since every path, gate and experiment it references is keel's. There +is deliberately no copy under `~/.claude/skills/`; a second copy is a second source of truth, and +the two drift silently. + +`.claude/` is gitignored (`.gitignore:34`), so this file is tracked by explicit `git add -f` — the +same exception the two `review-fix-merge-pr` skills already use. **Being gitignored does not mean +untracked here; check before assuming an edit is uncommitted.** + +Version control matters more for this file than for a typical skill, because most of it is not +procedure but *recorded reasoning* about measurements that cost real compute to produce — why +stationarity is never a filter, why a small-sample profit factor is a lottery ticket, why the +engine's own invariants are unobservable from a proposer. An untracked file loses the argument +along with the rule, and the next person re-derives both the hard way. + +## Why the constraints exist + +- **No embedded LLM/API key inside keel.** The design is an "outside-first hybrid": keel stays + dependency-free, secret-free, and fully unit-testable. All non-deterministic scouting (this + skill) happens outside keel; keel owns only ingest → screen → report + (`keel assets propose`, backed by `keel/proposer.py`). +- **Mandatory citations, enforced only at the schema level.** `proposer.py`'s `_is_http_url` + checks that a source is a well-formed `http(s)` URL — it does **not** and cannot check that + you actually read it. A fabricated-but-well-formed URL passes keel's validation silently. That + gap is why *this skill*, not keel's code, is the only thing standing between a plausible-looking + lie and a human's attestation decision. Treat citation honesty as load-bearing, not a formality. +- **Closed `RULE_REGISTRY`.** `keel/agent.py` maps exactly four rule kinds to Python classes and + raises `ValueError` on anything else. A new rule kind is a human-reviewed change to the + deterministic trading core, not something this skill can propose into existence — this is what + keeps the §5 asymmetry intact for strategies, the same way `attestation=None` fails closed for + assets. +- **`attestation=None` fails closed.** Sector/backing/yield cannot be derived from candles. + `shariah_hypothesis` is explicitly UNVERIFIED and is never passed to `screen_asset` — it exists + so the human attesting has a head start, not so the gate can be skipped. +- **Taker math controls the field.** Round-trip friction here is ~2.5% of notional, the same order + as the per-trade edge of everything ever measured, and it is levied on the *search* rather than + on the *edge* — an account pays ~241 tolls to be present for the handful of trades that pay. As + of the 2026-08-13 re-measurement the intersection of `n>=100`, gross PF > 1.0 and net PF > 1.0 + is **vacant across the entire 24-asset universe**, at the maker tier as well as the taker one. + A candidate is not interesting because it is liquid and admissible; those are entry conditions, + not evidence. See "What has already been measured" below before writing anything. + +## Tiering — route every step to the cheapest model that can do it + +| Step | Model | Job | +|---|---|---| +| Sweep Coinbase USD spot listings, web search, read sources, write raw findings to scratch files | **Haiku** | Breadth. Cheap, parallel, no judgment calls. | +| Shariah plausibility (sector/backing/yield), liquidity plausibility, drift vs. prior runs | **Opus** | Judgment. One agent, reads all Haiku output + prior run history. | +| Emit the final JSON in keel's exact schema | **Sonnet** | Composition. Validates every field against the rules below before writing. | + +Dispatch with the `Agent` tool, `model` param set per tier, in parallel where independent (see +`superpowers:dispatching-parallel-agents`). Give each Haiku agent a narrow, self-contained brief +(one venue sweep, or one small cluster of candidate assets) — Haiku doesn't need context, it needs +a fetch-and-report task. + +## Output 1 — asset candidates + +**File:** `~/keel/proposals/YYYY-MM-DD-shortlist.json` (today's date; if the file already exists +for today, treat this as a rerun and overwrite it — the run log is what accumulates). + +**Schema — exactly what `keel/proposer.py::parse_proposal` accepts:** +```json +{ + "candidates": [ + { + "asset": "SOL", + "rationale": "High developer activity and liquidity; ...", + "sources": ["https://www.coinbase.com/price/solana", "https://coinmarketcap.com/currencies/solana/"], + "shariah_hypothesis": "utility L1, no interest-bearing mechanism" + } + ] +} +``` + +Rules `proposer.py` enforces (violate these and the entry is dropped into `invalid`, never +screened — match them before writing): +- `asset`: non-empty, **alphanumeric only** — `SOL`, never `SOL-USD` or `wSOL`. (`_candidate_error` + checks `.isalnum()`; a dash builds a malformed product id downstream.) +- `rationale`: non-empty string. +- `sources`: **at least one** string that parses as `http(s)://...netloc...`. **Every URL here + must be one you (or a Haiku sub-agent) actually fetched this run** — quote the exact URL your + fetch tool returned, never one recalled from training or guessed by pattern (e.g. + `coinmarketcap.com/currencies//` is a plausible-looking guess, not a citation, unless you + fetched it and it resolved). +- `shariah_hypothesis`: optional, always UNVERIFIED, never a substitute for attestation. + +### Screen BEFORE composing — not after + +**Run `keel assets discover --probe-liquidity --probe-history` and drop everything marked `LOW` or +`NO` before a single candidate reaches the judgment step.** Composing first and screening later is +how the 2026-08-08 fifth run shortlisted BICO on a reported "$12.81M/24h" that the gate then +rejected at a median daily volume of 108,004 — 9× under the floor — and nearly dropped DOGE for +sitting 1.7% under a *discovery* floor while being ~30× over the *admission* one. + +`--min-volume-24h` is a **one-day venue snapshot**. The admission criterion is the **median of +`volume × close` over cached history** (`screen.py::median_daily_quote_volume`). Same units, wildly +different statistics — on a live sweep the snapshot ran 227× above the median for BICO and ~4× +below it for DOGE. **The 24h number is a cheap way to bound the request count and nothing more; +never treat it as a proxy for liquidity.** On that same sweep, three of the top fourteen by 24h +volume (BICO, IMU, GWEI) were sub-floor on the real criterion. + +`--probe-liquidity` computes the gate's own statistic, one extra request per candidate. Two honest +limits, both of which belong in the run log if they bite: + +- It samples the **last 180 days**; the screen medians over **all** cached history. On that sweep + ZEC probed at 33.1M against a full-history median of 1.23M — a 27× spread, because ZEC surged + recently. So `ok` means "worth pulling candles for", never "will be admitted". +- The recent window makes a false `LOW` unlikely for a currently-active asset, but one that was + liquid years ago and is quiet now can probe `LOW` and still clear the gate. If you drop + something on `LOW` alone, name it in the run log so it can be revisited. + +Anything that survives and has cached candles should then go through `keel assets screen`, which is +fully offline. **Attestation should be the only rejection a shortlist entry still carries by the +time the operator reads it.** + +### Pre-filter before anything reaches Opus/Sonnet + +- **Spot only.** Rails 18/19 (`keel/compliance/screen.py`'s `spot_instrument` criterion, via + `parse_spot_product_id`) admit **USD-settled SPOT only**: `BASE-USD`, uppercase, exactly one + hyphen. Never propose a perp, a dated future (`BTC-28AUG26-CDE`), an index/basket, or an + FX/commodity wrapper — these fail mechanically and waste the human's attestation time. +- **Already allowlisted.** Read `allowlist:` from `~/keel/config.live-sandbox.yaml` **at run + time** — do not hardcode it (it changes; at last read it was `BTC, ETH, PAXG, ADA, XLM`, but + trust the file, not this sentence). +- **No source, or source you didn't fetch** → drop before composition, don't emit a candidate + with a placeholder citation. + +## Output 2 — re-examine prior runs (keel has no rejection ledger) + +keel's `asset_attestations` and `screen_exceptions` tables don't persist scout-time outcomes — +there's no "why did we reject SOL last time" anywhere in keel's DB, and there deliberately never +will be (§ Non-goals: "No proposal-audit DB table in v1... YAGNI until an audit trail is actually +needed"). So **this skill keeps its own history**, outside keel, in `~/keel/proposals/`: + +- The dated shortlist files themselves (`YYYY-MM-DD-shortlist.json`) are the record. +- **`~/keel/proposals/run-log.md`** — append (never rewrite) a dated section each run: + - **New**: assets in today's shortlist not in the previous one. + - **Dropped**: assets in the previous shortlist not in today's — and why, if known (thin + liquidity that never deepened, sector concern, no longer worth the operator's attestation + time). + - **Changed**: assets present in both runs whose `rationale`/`shariah_hypothesis`/sourced + liquidity picture materially shifted (e.g. "thin-liquidity asset that has since deepened," "a + yield mechanism that changed"). This is Opus's judgment call, made by diffing today's Haiku + findings against the last shortlist file, not a mechanical string diff. + +To diff: list `~/keel/proposals/*-shortlist.json`, sorted by filename (dates sort lexically), +take the most recent one before today's, hand both to the Opus judgment step. + +## What has already been measured — read this before proposing anything + +**Read `~/Development/work/CodeGate/keel/docs/experiments/` before writing any output.** As of +2026-08-13 the shipped strategy library has been measured to exhaustion, and a proposal that +re-treads it wastes the operator's time. The state of play, with the documents that establish it: + +- **Every signal rule the codebase ships has been measured at its shipped constructor defaults + across all 24 assets with hourly history** (`2026-08-12-shipped-defaults-intersection.md`), and + `rsi_meanrev` additionally along its own frequency axis + (`2026-08-12-rsi-meanrev-scale-vs-selectivity.md`). Both were re-run on a corrected engine + (`2026-08-13-restated-under-a-production-faithful-engine.md`) — read that one first, since the + other two carry numbers their engine produced and are annotated rather than restated. +- **No asset-rule-parameter combination is simultaneously measurable (`n>=100`), gross-positive, + and net-positive at any fee this venue offers.** Zero of 90 in the intersection matrix, zero of + 82 in the rsi grid — including the 0.6% maker tier the account cannot currently reach anyway. +- The three rules fail for **three unrelated reasons**: `turtle_breakout` has real, broad gross + edge destroyed by cost; `pullback_continuation` has essentially none (median gross 0.77); + `rsi_meanrev` has the best gross distribution of the three but never fires enough to be + admitted, and loses that edge the moment it is made to. + +### Asset expansion is refuted as a PERFORMANCE fix — and still valid for power + +This matters for what this skill is *for*. `turtle_breakout` is negative on every one of the 24 +assets it has been measured on, so **adding a 25th cannot fix a rule negative on all of them.** +Never justify a candidate on the theory that it might be the one that works. + +What expansion is still legitimately for, and why this skill is not retired: + +- **statistical power** — more assets means more independent series for a walk-forward or a PBO + matrix, which is a different argument from "one of them will be profitable"; +- **the compliance pipeline** — attestation, screening and the allowlist are real work regardless + of whether any rule is currently viable; +- **future rules** — a technique from §3b that does not yet exist will need a universe to test on. + +Say which of these a shortlist serves. A shortlist implying "this asset may be profitable under +the current rules" is contradicted by measurement. + +### Cost is the binding constraint, so every performance claim needs its cost regime + +Round-trip friction on this venue is **~2.5% of notional** (1.2% taker per leg plus 5bp slippage +each way). That is the same order of magnitude as the per-trade edge of everything measured, and +it is what killed every otherwise-promising result — seven assets showed gross profit factors +above 1.0 and **all seven died at the maker rate**, before the taker rate actually paid was even +reached. + +- **A performance number without its cost assumption is unusable.** A result developed on equities + at 5bp, or on crypto perps at 2bp, does not transfer to a 1.2% taker venue, and nothing in the + abstract will say so. Extract the cost regime, or state that the source omits it — the same + standard this skill already applies to replication status. +- **Prefer techniques that reduce trade count or raise per-trade edge** over ones that raise win + rate. `pullback_continuation` wins 52–75% of its trades at a profit factor near 1.0 — small wins + against small losses — which is the profile most completely destroyed by a fixed per-trade toll. + A high win rate is not evidence of robustness here; it is frequently the opposite. + +### Thin assets carry flattered backtests + +The liquidity guidance above governs *admission*. It does not govern *accuracy*. `backtest()` +applies one global `slippage_pct = 0.0005` to every product from BTC to TON (issue #259), so an +asset at the thin end is credited with execution it cannot achieve — by an unmeasured amount, and +always in the favourable direction. When a shortlist entry sits near the liquidity floor, say so +in the rationale: its eventual backtest will look better than it should. + +## Output 3 — strategy research (two outputs; the split is load-bearing) + +Scout recent quant/finance publications, university material, and reputable quant blogs. Every +citation, for both outputs below, must include **publication date** and note **whether the result +was independently replicated** (state "not stated" if the source doesn't say — quant literature is +full of unreplicated backtests, and omitting that flag is worse than not citing at all). + +### 3a. Parameter proposals — backtestable today + +**File:** `~/keel/proposals/YYYY-MM-DD-param-proposals.json`. Only for the four registered kinds +— nothing else is backtestable without a core code change: + +| kind | class (dev repo) | example tunable params | +|---|---|---| +| `turtle_breakout` | `keel/strategy/rules/turtle_breakout.py::TurtleBreakout` | `entry_lookback`, `exit_lookback`, `adx_threshold`, `atr_stop_mult`, `target_rr` | +| `dca` | `keel/strategy/rules/dca.py::Dca` | `cadence_days`, `budget_usd`, `dip_bonus_pct`, `lookback_days` | +| `pullback_continuation` | `keel/strategy/rules/pullback_continuation.py::PullbackContinuation` | `ema_periods`, `entry_zone`, `stop_method`, `target_method` | +| `rsi_meanrev` | `keel/strategy/rules/rsi_meanrev.py::RsiMeanReversion` | `oversold`, `overbought`, `rsi_period`, `atr_mult`, `fixed_rr` | + +```json +{ + "proposals": [ + { + "kind": "turtle_breakout", + "params": {"entry_lookback": 55, "atr_stop_mult": "2.5"}, + "rationale": "why these values, tied to the citation below", + "not_already_covered": "why this is not inside a grid already swept -- name the experiment document checked", + "citation": { + "url": "https://...", + "publication_date": "2024-03", + "independently_replicated": false, + "cost_regime": "the fees/slippage the cited result assumed, or 'not stated'", + "sample_size": "trades the cited result rests on, or 'not stated'", + "evaluation_window": "the period it was measured over, or 'not stated'" + } + } + ] +} +``` + +**`not_already_covered` and `cost_regime` are required, and both exist because of measured +history.** A `turtle_breakout` proposal of `entry_lookback: 55` is inside a grid that has already +been swept — 144 configurations across six assets, whose mean-across-assets winner then +transferred to eighteen unseen assets and reproduced its *losing* profit factor to within 0.034. +Proposing another cell of that grid is not new information. If a proposal lands inside swept +territory, either drop it or say explicitly what makes this cell different from the ones measured. + +`cost_regime` is the same discipline this skill already applies to `independently_replicated`: a +number whose assumptions cannot be recovered is not evidence. State "not stated" when the source +omits it — that is a real answer and a useful one. + +**Frame any performance claim against the fill model the human's backtest will actually use.** +Since #258, `backtest()` fills an entry at the **next bar's open plus slippage** — a market order, +because that is what `execution/executor.py` places live (`order_type="market"`, +`limit_price=None`). It does *not* rest an order and wait for a level. Two consequences for a +proposal: + +- **A technique that assumes a resting limit or stop entry will not be executed as described.** Its + edge, whatever the source measured, depends on getting a chosen price; keel takes the next open, + favourable or not. `pullback_continuation` is the worked example — its `entry = signal_bar.high + + buffer` demands follow-through, and a market fill takes the trades it meant to decline, which + doubled its trade count and dropped its gross PF from 0.92 to 0.77. +- **Say so when a proposal depends on entry timing.** That is a real finding for the operator and + it points at issue #260 (making the executor honour a conditional entry), not at a parameter. + +**`sample_size` and `evaluation_window` exist because both traps have already bitten this project, +and both are visible in a paper's own abstract if you look.** + +- *Sample size.* `rsi_meanrev` showed the best gross profit factor of the three shipped rules — + median **1.1631** — on a median of **38 trades**. Forced to fire enough to be admitted, the same + rule collapsed to **0.8396** across 82 cells at `n>=100`, and not one was net-positive at any + fee. That was not a decline as trades accumulated; it was a level shift at the floor. **A + headline profit factor on a small sample is a lottery ticket, not an edge.** If a source does + not state its trade count, say so — that omission is itself the finding. +- *Evaluation window.* ZEC-`turtle` printed **1.555 gross** over five years while running **three + consecutive losing years** and compressing 92.7% of its lifetime PnL into 2025–26. A result + measured over one regime is a claim about that regime. Record the window so the operator can see + whether the claim spans more than one. + +Report these; do **not** invent a rejection threshold from them. The counter-example is in the +merged record: ZEC under `pullback_continuation` is the only asset-rule pair in the study with **no +losing complete year** — and a gross profit factor of **0.875**. Perfectly stationary at losing +slightly, reliably. A stationarity gate passes that and rejects ZEC-`turtle`, which at least made +money gross. Stationarity is a diagnostic, never a filter. + +This skill still never runs a backtest — it frames the numbers so the human's run is interpretable. +Verifying the engine's own invariants (the pending-lifespan assertion, the intent-divergence log) +belongs to whoever runs that backtest and that live cycle; both are unobservable from here. The +lifespan assertion in particular cannot be "checked in the log": it raises, so a violation ends the +run rather than printing anything. + +Verify each param name against the class's actual constructor in the dev repo before writing — +don't invent a kwarg. `keel rules seed` only seeds each kind's *constructor defaults*; turning a +proposal into a backtest today means a human either edits a seeded row's params or uses +`Repository.insert_rule` (`keel/data/repository.py`) directly, then runs `keel rules backtest +` or `keel simulate`. That mechanic is the human's call — this skill only supplies the +params + citation, never runs a backtest or seeds a row itself. + +### 3b. Research briefs — genuinely new techniques + +**File:** `~/keel/proposals/YYYY-MM-DD-research-briefs.md`. For anything `RULE_REGISTRY` doesn't +implement. Each brief: what the technique is, the evidence (with date + replication status), what +implementing it in keel would require (roughly — new `Rule` subclass, registry entry, human +review), and honest weaknesses/failure modes. **Never write or propose code for the trading core.** +A brief is prose and citations only — no diffs, no Python, not even a sketch that reads like a +patch. If you're tempted to draft the class, stop: that's the exact boundary that keeps activity +gated on human review. + +## Worked examples + +**Good candidate entry** (goes in the shortlist): +```json +{ + "asset": "SOL", + "rationale": "Top-10 by market cap, deep USD spot liquidity on Coinbase, 5+ years of price history, base SOL-USD listing carries no lending/staking-derivative wrapper.", + "sources": [ + "https://www.coinbase.com/price/solana", + "https://coinmarketcap.com/currencies/solana/" + ], + "shariah_hypothesis": "UNVERIFIED: utility L1 smart-contract platform (sector: infrastructure); native base-layer token, not a claim on an issuer (backing: native); base SOL carries no protocol-level guaranteed yield for bare holding — this is distinct from staked-SOL derivatives, which are NOT this candidate and should be scouted separately if ever considered." +} +``` + +**Correctly rejected — never reaches the shortlist:** +- `SOL-PERP` or `BTC-28AUG26-CDE` surfaced during the Coinbase sweep — dropped at the mechanical + pre-filter (not spot; rail 19 vetoes it regardless of what a human decides), never even reaches + the Opus judgment step. Log this in the run log's "considered, mechanically excluded" note so + the operator knows the sweep saw it and why it's absent. +- A DeFi lending-protocol governance token whose Haiku research surfaces a documented + interest-bearing yield mechanism — Opus drops it before composition and logs why ("plausible + `riba_yield`/`haram_sector` per §28.4; not worth the operator's attestation time"). This is a + judgment call to save the human time, **not** a substitute for `screen_asset` — if the operator + disagrees, they can still hand-run `keel assets screen` on it themselves. + +## Do NOT + +- **Fabricate a citation.** Every URL in every output must be one a fetch tool actually returned + this run. A well-formed-but-unfetched URL passes keel's schema check and is the single most + damaging failure mode this skill can produce — the human trusts it precisely because it looks + real. +- **Propose a non-spot instrument.** No perps, dated futures, indexes, or FX/commodity wrappers — + ever, regardless of liquidity or narrative. +- **Write or propose code into the trading core.** No `Rule` subclass drafts, no `RULE_REGISTRY` + edits, not even "just as an example" — that's a human-reviewed change, full stop. +- **Write to any `keel-*.db`, `config*.yaml`, or anything under `~/keel/` other than + `~/keel/proposals/`.** +- **Run `keel assets attest`, edit `allowlist`, or place/confirm a trade.** Attestation and + admission are the human's call, downstream of your output, never inside this skill. +- **Hardcode the allowlist.** Read it from `~/keel/config.live-sandbox.yaml` every run. +- **Pass `shariah_hypothesis` (or any LLM judgment) into an admission decision.** It's a head + start for the human, labeled UNVERIFIED, and nothing more. + +## Rationalizations to catch yourself making + +| Excuse | Reality | +|---|---| +| "I recall this fact, close enough to a citation" | Memory isn't a source. If you didn't fetch a URL this run, it isn't a source. | +| "This URL pattern is probably right" | Never construct a plausible URL. Paste back exactly what the fetch tool returned, or omit the candidate. | +| "One candidate's missing a source, I'll just drop it quietly" | Name it and the reason in the run log — same spirit as `proposer.py`'s own `invalid` list: excluded, never silently vanished. | +| "It's basically the same asset as the perp, just propose the base" | Fine — but only if you can independently source the spot listing itself, not by relabeling the perp finding. | +| "I'll sketch the Rule subclass so the human can see what I mean" | That's code into the core. Describe it in prose in the research brief; do not write it. | +| "This asset looks strong — it might be the one the rules finally work on" | `turtle_breakout` is negative on all 24 assets measured. A 25th cannot fix a rule negative on every one. Justify candidates on power, compliance or future rules — never on hoped-for performance. | +| "The paper reports a profit factor of 1.8, that's well above break-even" | Above break-even *at its own costs*. At 2.5% round-trip, seven assets with gross PF > 1.0 all died at the maker tier. A number without its cost regime is not evidence. | +| "High win rate, so the edge is robust" | Often the opposite here. `pullback_continuation` wins 52–75% at PF ≈ 1.0 — small wins against small losses, the profile a fixed per-trade toll destroys most completely. | +| "This backtest looks clean, the numbers are all plausible" | Two engine defects (#254, #256; #257, #258) produced plausible, internally consistent output for the life of the project while 2,712 tests passed. Plausible output is not evidence of a working engine — check what the numbers *cannot* distinguish. | + +## After writing outputs + +Report to the operator: what's in each file, the run-log diff (new/dropped/changed), and the +explicit next human steps — `keel assets propose --from ~/keel/proposals/-shortlist.json`, +then `keel assets attest ...` for anything worth pursuing, then `keel fetch` + `keel rules +backtest` / `keel simulate` for strategy params. Never phrase the report as "I added" or "I +proposed for trading" — you scouted; the human decides.