Coil Scan — dual-timeframe %R Trend Exhaustion
SkillDev toolsDaily scanner that finds stocks sitting in the "bottom of the coil" using a dual-timeframe Williams %R Trend Exhaustion indicator, then flags the ones that just triggered out. Silent data feed for Michael, he writes trade posts only when he likes a name. Use when the user asks about "coil scan", "bottom of the coil", "what's coiled", "daily coil", "%R exhaustion scan", "what's oversold and turning", or wants to find deeply oversold names that are just starting to reverse.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Coil Scan skill
What this skill tells your AI
The instructions your AI receives, as published by mphinance/alpha-skills in skills/coil-scan/SKILL.md and read by ahel’s review.
Mirrors the "%R Trend Exhaustion [upslidedown]" TradingView indicator (dual timeframe 21 + 112). Finds stocks that are both deeply oversold AND just starting to reverse.
The indicator math
- %R(n) = Williams %R =
100 * (close - highestHigh_n) / (highestHigh_n - lowestLow_n), range −100…0. - Fast leg = EMA(7) of %R(21).
- Slow leg = EMA(3) of %R(112).
- Threshold (TH) = −80.
- In the coil: both fast AND slow ≤ −80. The short-term AND the structural picture are both deeply oversold. This is the stalk zone — a name can sit here for days/weeks before the trigger fires.
- Triggered out: fast crossed back above −80 while it was recently (within ~20 bars) coiled on both legs. The slow leg still lagging confirms the structural exhaustion hasn't reset — this is the bullish reversal signal.
Two output buckets
⚡ TRIGGERED OUT (≤3 days ago)
Fast just crossed back above −80. These are actionable reversal candidates.
A sanity check filters false-positives:
- If the close is already >5% above the price on the trigger date → tagged "already ran — not fresh" and downranked.
- If the close is now above EMA21 → same tag.
The canonical false-positive this catches: SLB crossed up but had already run +4% to just under its 21EMA by the time the scan ran — that's a chase, not an entry. It lands in the sanity-filtered section, not the fresh list.
🟢 IN THE COIL (stalk candidates)
Both legs ≤ −80, no trigger yet. Sorted deepest-first (most negative slow leg = most structurally exhausted).
The days-in-coil counter comes from the persistent state file
(state/coil_watch.json), so the scan can say "IREN — day 6 in the coil."
A name stays in the store until it triggers or exits the coil entirely.
Per-name display
SYM fast%R slow%R depth close day-in-coil vs SMA200 vs EMA21
IREN -92.3 -88.1 -8.1 $7.42 day 3 below SMA200 below EMA21
- depth:
min(fast, slow) − (−80). Negative = deeper in the hole. - above/below SMA200 + EMA21: from live technicals endpoint.
How to run
From the repo root or anywhere under it:
python .claude/skills/coil-scan/scripts/coil_scan.py
Options:
# smoke test against a short list
python coil_scan.py --universe IREN,RGTI,SMCI,SLB,FIG,GLD,ASTS
# limit universe size
python coil_scan.py --limit 50
# write JSON output
python coil_scan.py --json /tmp/coil_2026-07-19.json
# custom universe file (one ticker per line, or comma-separated)
python coil_scan.py --universe /tmp/my_watchlist.txt
Universe defaults to: all live TDPro screeners + stock-recap persistent watch
list (falls back to the most recent stock-recap/runs/*/raw/screener_*.json
if the live API is unavailable). Aim: ~150–200 names.
Persistence
state/coil_watch.json is keyed by ticker and tracks:
firstSeen— date first seen in the coildaysInCoil— rolling counter updated each runentryClose/lastClose— price anchorstriggered/triggerDate— when the cross fired
On each run: newly-coiled names are added, existing names increment their counter, triggered names are flagged, and names that left the coil without triggering are dropped.
Data sources
- Bars:
GET /api/agent/ticker/{SYMBOL}/chart-data?days=365(TraderDaddy Pro Railway API; Bearer token from.env_agent_api). Needs ≥115 bars for the slow %R(112) leg. - Live technicals:
GET /api/agent/ticker/{SYMBOL}(close, ema21, sma200, atr, rsi — used for sanity check + display). - Universe:
GET /api/screeners+GET /api/screeners/{id}/run× 10.
Interpreting results for Michael
- Empty IN THE COIL list: tape is not oversold broadly — that's fine, nothing to stalk beats a list of knives.
- Triggered + already ran: it moved without you. Note it for the next pullback but don't chase.
- Triggered fresh + below EMA21: highest-quality entry zone. Fast is turning, structure is still washed out, price hasn't reclaimed the mean yet.
- In coil day 10+: structural exhaustion is deep. The longer it coils, the more compressed the spring. Watch for the fast leg to tick back above −80.
The scan is a data layer — Michael confirms setups on Ghost Flow (the %R EXHAUST panel is visible there) before sizing.
Trigger A/B harness (coil_ab.py)
A sibling script runs two trigger rules side by side so we can settle whether tightening the trigger helps:
- CURRENT (
find_trigger): both legs were ≤ −80, then fast crosses > −80. Slow is not required to still be under −80 at the cross. - STRICT: fast crosses > −80 while slow is still ≤ −80 at that same bar ("fast breaks while slow still under"). A subset of CURRENT.
A 2026-07-21 backtest found STRICT is worse — the crosses it drops (slow already recovered = both legs lifting together = structural confirmation) outperformed at every horizon (3/5/10/20d). Fast-leads-while-slow-buried is the earlier but weaker (knife) signal. So the harness is a live forward test to confirm that before deciding.
python coil_ab.py # log today's fresh triggers under both rules
python coil_ab.py --report # re-price the ledger, print STRICT vs CURRENT-ONLY
Ledger: state/ab_ledger.json (deduped per symbol|trig_date). Logs daily via
weekday 22:00-UTC cron → state/ab_cron.log. Core coil_scan.py is untouched.
Signals
- GitHub stars
- 27
- Forks
- 5
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
coil-scan- Source
- github.com/mphinance/alpha-skills