| name | nis-stock-breakdown |
| description | Produce an Instagram-ready breakdown of a single stock from a swing-trading perspective, anchored on the NIS Momentum setup. Use this whenever the user wants to "break down", "review", "make a post about", "do a setup on", or "analyze" a stock/ticker for swing trading — even if they don't say "Instagram". Renders an annotated technical chart (candles + volume highlights + entry/stop/target levels), surfaces the fundamentals that matter, and lays out a concrete proposed trade (entry, stop, target, R:R) derived from the NIS Momentum signals, then assembles a carousel + caption. Trigger on phrases like "break down NVDA", "swing setup for $TSLA", "make a post about this stock", "what's the NIS momentum read on X", or when a ticker is shared for a trade-idea breakdown. |
NIS Stock Breakdown
Turn one ticker into an Instagram-ready breakdown that shows the setup, the chart,
the fundamentals, and the trade — read the way the NIS Momentum screen reads a
stock. The deliverable is a carousel (slides + caption) plus a narrated reel built
around an annotated technical chart.
The audience is retail swing traders: smart, time-poor, allergic to hype. The
breakdown has to be sharp, specific, and grounded in real numbers — not "this
stock looks bullish". Every claim ties to a value the screener produced. The thesis
is the chart; fundamentals are the permission slip; the trade is concrete and honest
(including whether it's actionable today or just a watch).
Step 1 — Pick the stock
Two ways in:
- A ticker is given ("break down NVDA"): use it directly, but still run it through
the reel-ability gate below and tell the user if it's a weak reel candidate.
- No ticker / "what's on the NIS Momentum board": run the screening and pick a
name. The screen lives at
code/analytics/services/market_screenings/scripts/nis_momentum.py; run it via
the market-screenings runner/CLI, or pull the latest stored results the
/screenings UI reads. Each row carries symbol, RS_Rank, sector,
within_buy_range, extended, accumulation, PASSED_FUNDAMENTALS. Note its real
RS_Rank to pass along in Step 2.
The reel-ability gate — pick for views, not just for the screen
The biggest lesson from past reels: passing the screen ≠ making a watchable reel.
The two best-performing reels (ENVA 345 views, NWPX 219) were clean, higher-priced
momentum names whose chart was visibly launching and whose data let the narration
stay 100% confident. The two worst (RLJ 49, HOFT 29) passed the same screen but were
hard to pitch without hedging — and the hedge killed the scroll.
The gate has two layers. Layer A is pure NIS Momentum methodology, expressed in the
screen's own fields and thresholds (see references/nis-momentum-framework.md) — a name
that's weak here is a weak NIS setup, not just a weak reel. Layer B is an editorial
overlay for views that sits on top — explicitly not part of NIS, so never present
it as the screen's verdict.
Layer A — NIS-methodology quality (use the screen's own constants)
Every board name already cleared RS > 80, the full Minervini stack, near-highs, and
PASSED_FUNDAMENTALS. For a reel, verify where in the setup it is and whether volume
confirms, in methodology terms:
- Position in the buy range. Prefer
within_buy_range (pivot → +5%) or below_pivot
but coiled — small negative extension_pct (roughly ≥ −5%) with
vol_contracting_in_base true (the classic pre-breakout coil). Avoid extended
(>5% above pivot — the framework says don't chase; a chase reel ages badly) and
deep-below-pivot watches (e.g. HOFT at extension_pct −7% — a legitimate NIS watch,
but the breakout isn't imminent, so there's no urgency to film).
- Volume conviction — NIS's "conviction tell". Require
accumulation true
(up_down_vol_ratio ≥ 1.25). If the name is within_buy_range (you're calling a
live breakout), it also needs vol_ratio_today ≥ 1.4 — the framework's
institutional-interest bar. This is the methodology-correct reason RLJ was weak: it
was in the buy range but printed vol_ratio_today ≈ 0.4× — a breakout on
below-average volume is an unconfirmed breakout by NIS's own rule, so the pitch is
forced to hedge. (For a below_pivot coil, low vol_ratio_today is expected and
fine — but then narrate it as a watch, not a confirmed move.)
- ADR in the swing band.
adr_pct 3–15% is the framework's sweet spot — 6% is
fine (HOFT's ADR was never the problem), and a 4–8% stop is just the methodology's
own 1.5×ADR clamp, not a flaw. Only flag adr_pct <2% (too slow — a flat-looking
chart) or >15% (too wild).
- Leadership signal (best hooks are the truest). A real
RS_Rank near the top
(IBD 1–99, higher = stronger, so top-decile ≈ ≥ 90) and/or rs_line_new_high true
(leadership confirmed before price) are the most methodology-honest scroll-stoppers —
prefer names that have them. (Both are auto-fetched from the latest NIS Momentum
screening in Step 2 — see the RS caveat.)
Layer B — reel-ability overlay (editorial, NOT NIS — for views only)
- Chart shape (most important). The reel lives on
chart.png: you want a visibly
accelerating uptrend with recent candles pushing into the pivot (ENVA, NWPX).
Reject choppy/range-bound charts or a red/falling last candle (HOFT). This is the
visual expression of Layer A's "coiled near the pivot, volume confirming" — if Layer A
is clean, the chart usually looks the part.
- Price legitimacy. Prefer price ≥ ~$30. NIS has no price floor (RLJ $11 and
HOFT $15 passed it fairly), but sub-$20 names read as "cheap" to this audience — the
two losers were the two cheapest. An editorial preference, not a screen rule.
- Hook-able fundamentals. A clean positive P/E with a clear positive earnings story
photographs better than a turnaround. NIS legitimately passes a loss→profit
turnaround (it can satisfy
increasing_eps + beat_estimate), and the framework
treats fundamentals as supporting cast — but a turnaround makes a weak hook (it
admits the company was broken). Prefer clean names; this is a reel choice, not a
fundamental gate.
If a user-given ticker fails the gate, build it if they insist — but say plainly which
layer/criterion it failed (and whether that's a NIS weakness or just a reel weakness). If
you're picking from the board, take the name that's strongest on Layer A and clears
Layer B — not merely the most "actionable" one.
Step 2 — Gather the data + render the chart
One command does both — it runs the production screener and writes the chart plus a
setup.json you'll write every slide from:
cd code/analytics
.venv/bin/python ../../.claude/skills/nis-stock-breakdown/scripts/build_setup_chart.py \
--ticker NVDA --display-days 180 --rs-rank <rank from the screen>
Outputs under code/analytics/output/setups/<TICKER>/:
chart.png — 1080×1350 annotated chart: candlesticks, SMA50/150/200, volume bars
(amber where volume ≥ 1.4× its 50-day avg), the buy-range band, and entry/stop/
target lines.
setup.json — price, technical (RS, SMA stack, pivot, extension_pct,
within_buy_range/extended, adr_pct, vol_ratio_today, up_down_vol_ratio,
accumulation, rs_line_new_high, SMA values), fundamentals
(increasing_eps, beat_estimate, PASSED_FUNDAMENTALS), and the derived
trade_setup (entry / stop / target_2r / target_3r / risk_pct / R:R / status).
RS caveat: RS rank is universe-relative and can't be computed for one ticker, so
build_setup_chart.py auto-fetches the latest stored NIS Momentum screening for the
ticker (Supabase market_screening_result_rows → market_screenings.script_key = 'nis_momentum') and fills RS_Rank (IBD 1–99, higher = stronger), RSOver70, and
rs_line_new_high. setup.json.rs_rank_source records the scan date. Pass --rs-rank
only to override with a fresh value. If no stored screening exists, the rank is marked
unknown — don't state one.
For richer fundamentals (margins, growth, valuation) or a CEO/company profile, pull
from code/analytics/services/viral_reels/data_sources.py (company_profile,
fmp_quote, news_pulse). Keep it tight — two or three numbers.
Step 3 — Read the framework
Read references/nis-momentum-framework.md. It explains exactly how to interpret
every field in setup.json across the four aspects (setup / chart / fundamentals /
trade) and how the trade is derived. Anchor the whole breakdown in those values —
where the stock is in the setup right now (basing, in the buy range, or extended)
drives what the post tells the reader.
Step 3.5 — The narrative spine: the Hot Take Arc (non-negotiable)
The single structural lesson from past reels. The winners (ENVA, NWPX) and
losers (RLJ, HOFT) all opened a loop in their hook. The difference: the winners
closed the loop with a conviction pivot, while the losers opened the loop and
then deflated it with caveats ("one honest caveat: today's volume came in
light…", "two honest caveats…"). A deflated pivot betrays the promise the hook
made, and the scroll dies. HOFT had one of the best hooks of the four and still
flopped — because the body retracted it.
Every caption and every reel VO is written on the Hot Take Arc:
HOOK → Contradict what they expect + open a loop + a clock (no hedging, < 12 words spoken)
BUILD → Steel-man the obvious read (earn trust) (1–3 lines)
PIVOT → Reveal the mechanism that makes it real (concrete numbers — volume, beats)
CLOSE → Resolve the loop: the trade, stated with conviction
CTA → Direct address — speak to the viewer's situation
The one hard rule: NO HEDGING IN THE PIVOT OR CLOSE. The PIVOT must resolve
toward conviction — "this is real, and here's the mechanism" (volume confirms,
earnings back it). Never argue against your own thesis in the persuasive spine.
Urgency is built into the HOOK — non-negotiable. A coiled NIS Momentum name is a
perishable setup: it breaks (or fails) within a short window — days, not months — so
the hook must carry a clock. The viewer has to feel that the move is imminent and
that watching later means watching from the sidelines. This is why we gate out
extended and deep-below-pivot names in Step 1 — only a setup that's genuinely about to
move can carry an honest time-pressing hook. Make the urgency real, not manufactured:
the stock is coiled at the pivot right now, so the breakout is the next thing that
happens, not someday. Lead with that compression and that countdown — "still basing,
but not for long", "one volume day from the breakout", "the watchlists haven't caught
this yet — they will by [next catalyst]". Never invent a deadline the data doesn't
support; the truth (coiled at the pivot, volume building) is the urgency.
- Risk and what-invalidates-it have exactly one home: the invalidation slide
(Slide 7) and one closing line. Never let "but it's not confirmed", "honest caveat",
"size smaller because…" leak into the hook, pivot, or close.
- If the data forces a hedge into the pivot (volume below average, far below pivot,
negative earnings), the ticker failed the Step 1 conviction-pivot gate — go back
and pick another name. The arc structurally cannot close on a weak setup; that is
the real reason RLJ and HOFT lost, not just their charts.
This arc drives Step 4 (carousel + caption) and Step 7A (reel script) identically.
Step 4 — Write the carousel
Call get_carousel_style_guide() (the carousel MCP) first and follow its voice +
cover-hook rules. Output 6–8 slides plus a caption, laid out on the Hot Take
Arc from Step 3.5. Adapt the count, but this is the spine:
SLIDE 1 — Cover / HOOK [arc: HOOK]
Contradict what they expect + open a loop + put a clock on it. A specific,
stakes-driven line, not a label. Lead with the single most surprising number or an
expectation-flip, and make the breakout feel *imminent* — the setup is coiled at the
pivot now and resolves in days, not months. The reader should feel they're early but
about to be late.
e.g. "NVDA just cleared its pivot on 1.4x volume — and most watchlists missed it."
e.g. "This base is days from breaking. RS 96, coiled at the pivot — and nobody's watching yet."
SLIDE 2 — The setup (why it screened) [arc: BUILD]
Steel-man the obvious read, then the NIS Momentum tell: RS rank, stacked MAs,
near highs, beats.
e.g. "RS rank 96. Stacked above the 50/150/200. Three straight earnings beats."
SLIDE 3 — The chart (price + volume) ← chart.png lives here [arc: PIVOT]
Narrate the mechanism: the base, the pivot, the volume signature.
Point at the amber volume bars and the accumulation read.
SLIDE 4 — Volume & price detail [arc: PIVOT]
The conviction tell. vol_ratio_today, up_down_vol_ratio, adr_pct — in plain words.
This must resolve *toward* conviction (it does, because Step 1 gated out weak volume).
SLIDE 5 — Fundamentals that matter
2–3 numbers only. Rising EPS, the beats, the sector it's leading. Positive only —
if the numbers aren't clean, the ticker should not have passed the Step 1 gate.
SLIDE 6 — The trade [arc: CLOSE]
Entry / stop / target / R:R from trade_setup, stated with conviction. Add the
position-size reminder. (Status is "watch" vs "actionable" — but frame it as the
plan, not a hedge.)
SLIDE 7 — The risk / what invalidates it ← the ONLY place caveats live
"Loses the 50-day on volume, the setup's done." One clean rule. Keep every hedge
here; never let it bleed into the hook, pivot, or close.
LAST — CTA [arc: CTA — direct address]
Speak to the viewer's situation, then drive to newsimpactscreener.com. Not "follow
for more". e.g. "If you're still waiting for a setup to look 'obvious', you already
missed this one. The screener flags them here → newsimpactscreener.com".
Then a caption written on the same arc (see the style guide's Caption section):
- HOOK — first 125 chars. Self-contained, scroll-stopping, no hedging. This is the
feed preview before "more".
- BUILD → PIVOT → CLOSE — 4–6 short paragraphs that ADD context beyond the slides.
The pivot paragraph delivers the mechanism (volume + earnings confirm); the close
states the trade with conviction.
- One caveat/invalidation line — and only one. If you find yourself writing "one
honest caveat" or "two honest caveats", stop: the ticker failed the conviction gate.
- CTA — direct-address "you" line, then
newsimpactscreener.com + disclaimer.
- 8–14 hashtags mixing broad (#swingtrading #stocks), niche (#$NVDA, the sector), and
method (#relativestrength #breakout). No emojis.
Step 5 — Render the carousel graphics
Two render paths — pick by what the user wants:
A) Premium editorial deck (default for a polished post). Use
scripts/build_breakdown_slides.mjs. It hand-authors 8 SVG slides (dark midnight,
ONE amber accent, green/red reserved for price/volume semantics, Outfit type, no
emoji, asymmetric/editorial per the taste-skill), embeds the real chart, and writes
them to <dir>/slides/. Slides: cover → setup (Minervini gate checklist) →
chart → volume detail → fundamentals (earnings-beat bars) → trade (price ladder) →
invalidation → CTA.
cd code/analytics
.venv/bin/python ../../.claude/skills/nis-stock-breakdown/scripts/build_setup_chart.py \
--ticker ENVA --bare --height 880 --chart-name chart_bare.png
node ../../.claude/skills/nis-stock-breakdown/scripts/build_breakdown_slides.mjs \
output/setups/ENVA
cd output/setups/ENVA/slides && for f in slide-*.svg; do rsvg-convert -w 1080 -h 1350 "$f" -o "${f%.svg}.png"; done
B) Handwritten "napkin" deck (fast, voice-matched). Call the carousel MCP
get_carousel_style_guide() then render_carousel(slides, topic, handle, caption, out_dir). Ship chart.png as its own image in the sequence.
Step 6 — Render the breakout-validation animation
cd code/analytics
.venv/bin/python ../../.claude/skills/nis-stock-breakdown/scripts/animate_breakout.py --ticker ENVA
--mode combo (default) renders ONE MP4: hook → real breakout (validated on volume)
→ fake-out anti-pattern → CTA outro. Use --mode validated or --mode fake for
single-scenario clips.
The opening hook is ticker-specific. standout() scans the data for the single
weird, probably-overlooked tell — an outsized earnings surprise that held, an
extreme up/down-volume ratio, volume drying up at new highs, a long beat streak, a
top RS rank — and frames it as something the viewer missed ("WHAT EVERYONE MISSED",
"HIDING IN THE TAPE"). The visual hook is built on that same fact (a
beat-vs-estimate bar gap, up-vs-down volume bars, a streak row, an RS-rank scale, …)
via draw_hook_viz(). So no two tickers open the same way. It only claims what the
data supports (e.g. negative EPS growth / negative P/E are never shown as an edge).
Step 7 — Assemble the high-value reel
This is the highest-leverage step. A reel lives or dies in the first 3 seconds —
not in the production quality, but in the hook. The narration architecture below
is non-negotiable: write it before passing anything to ElevenLabs.
7A — Write the reel script using the Hook-Value-Trade arc
Voice & copy — kill the AI tells (the taste-skill, applied to words)
build_chart_reel.py writes script.txt for you to record (no TTS by default). The
auto-generated lines are a solid draft — but treat them like the taste-skill treats a
generic UI: a draft you make yours. A real person reading > a robot reading the data.
The voice is one calm, certain trader telling one friend about a setup — opinionated,
unhurried, never an announcer.
Banned copy tells (the Inter-font of trading reels — they signal zero intent):
| ❌ Slop (reads the dashboard) | ✅ Human (says why it matters) |
|---|
| "Relative strength rank 92." | "It's outrunning ninety-two percent of the market — that's a leader, not a laggard." |
| "Volume says accumulation." | "And the volume tells you who's behind it — big money loading, not leaving." |
| "Trend: stacked and rising." | "Trend's clean — every average stacked and turning up. Nothing fighting it." |
| "Stop, 92.30." / "Target, 109. Two to one." | "Lose 92.30 and you're out, no argument." / "First target's 109 — two-to-one. Take some, let the rest run." |
| "Interactive Brokers Group, Inc." | "Interactive Brokers." |
| "That's the move." (hollow filler) | (cut it — say nothing or say something true) |
| "Here's what almost everyone missed on…" | (vary it — the formulaic opener is itself a tell) |
Rules: (1) the VO never reads the on-screen card — the card shows the number, the
voice says the why/stance/consequence; (2) contractions, fragments, real speech rhythm —
write for the ear, not the page; (3) one point of view (the trader has opinions); (4) cut
any word you wouldn't say out loud; (5) vary phrasing so two reels never sound the same
(the copy version of DESIGN_VARIANCE).
Fewer terms, more comparisons (the most important content rule)
A normal viewer doesn't know what "RS 92", "accumulation", "the pivot", or "stacked over the
50/150/200" mean — and a term they don't understand is a scroll. Explain every concept by
comparison or plain language, not by jargon. Translate:
| ❌ Jargon term | ✅ Comparison / plain |
|---|
| "RS 92 / relative strength" | the HOW IT STACKS UP slide — "+40% in six months while the S&P did 7, and even NVDA did 6" |
| "accumulation / 1.3× up-down volume" | "way more buyers than sellers — big money's stepping in" |
| "stacked over the 50/150/200" | "it's been climbing for months — every trend line points up" |
| "the pivot / buy point 96.75" | "the line it has to clear — 96.75" |
| "ADR 6%" | "it swings about 6% a day, so give it room" |
| "extended" | "it's already run, so you're chasing" |
Relative strength is now its own slide. build_chart_reel.py auto-inserts the HOW IT
STACKS UP comparison (the ticker vs NVDA/MSFT/AAPL/S&P, real returns) as the first body
beat — it IS the strength fact, in numbers anyone gets. So never use "RS / relative
strength" as one of your 3 points (it would be redundant jargon); spend those 3 on the
business angle, the buying, the earnings, the setup — in plain words.
No analogy the viewer has to decode. A clever metaphor ("it's a toll booth on trading")
is the same failure as jargon — if it needs a sentence to land, it's a scroll. Say the
literal thing instead: "it gets paid every time anyone trades — up market or down." The
only comparisons that work in 1.5s are to things everyone already knows (the S&P, NVDA, "a
toll" only if you're literally explaining a fee). When in doubt, plainest wording wins.
Claude writes the script: the hook + the 3 most notable things
The reel is hook → 3 notable things → trade → CTA. You (Claude) read setup.json
and choose + write it — the Python templates are only a fallback. Don't dump every
stat. Pick the 3 most notable, scroll-worthy facts about this specific company and
say each like a human.
Choosing the 3 (strength is already covered by the comparison slide — pick other
standouts): a striking earnings beat/streak, a vivid business-model angle ("toll booth on
trading volume"), heavy buying ("more buyers than sellers"), the coil-at-the-trigger tension,
a clear catalyst. Rank by "would a trader stop scrolling for this?" and lead with the most
arresting. Each point is {big, label, color, vo}: big = the card's number/word ("TOLL
BOOTH", "4×", "+55%"), label = a tiny plain caption ("wins on volume, not direction"),
vo = the spoken why — no jargon (see the comparisons table above).
The hook (1–3 s, and it must work as muted text — most scroll sound-off, which is why the
cover poster carries it in big type). Hook bank below adapts the opus.pro Instagram-hook
formulas to swing trading. Pro tip (from the source): rotate across 3–4 categories so your
feed doesn't develop pattern fatigue — don't open every reel with the same Shock formula.
Generate 2–3 across different categories and keep the one truest to this ticker's standout
fact. (Map --hook-style: a data/coil/question hook → tension; a contrarian/business-model
hook → curiosity.)
Direct questions — pose the one they're already asking:
- "What if the strongest stock in the market is one you've never traded?"
- "Want to know the #1 mistake traders make on breakouts?"
- "Did you know $TICKER is outrunning ninety-two percent of the market?"
Shock / surprise — flip an expectation, open a curiosity gap:
- "A boring broker just quietly became a top-10% market leader."
- "The biggest lie in trading: wait for the breakout to be 'confirmed.'"
- "Harsh truth — by the time a breakout looks obvious, you've already missed it."
Story / anecdote — a mini-narrative the viewer wants resolved:
- "The screener flagged this at the open. By noon it was breaking out."
- "I screened five thousand stocks this morning so you don't have to. This is the one."
- "Here's how this went from a watch to a breakout in a single session."
Problem / solution — name the pain, then the fix:
- "Buying breakouts that fail? Wait for this one thing instead."
- "You're losing trades because you buy here — instead of waiting for the break."
- "Most people overcomplicate entries. It's one level. That's it."
Authority / credibility — lean on the screener's edge (strong for this brand):
- "We screened the whole market this morning. One name stood out."
- "The data doesn't lie — this is the strongest setup on the board today."
- "Every leader that runs does this first, and $TICKER just did it."
Engagement / save-it (use sparingly, as on-screen text or the CTA — NOT the spoken hook;
"tag a friend / double-tap" reads spammy for a finance brand):
- "Save this before it triggers."
- "Stop the scroll — this is the setup you've been waiting for."
Avoid the worn "here's what nobody's talking about" unless you make it concrete and specific.
Write it to output/setups/<TICKER>/script.json and render with --script (no --hook-text
needed — the script carries the hook):
.venv/bin/python ../../.claude/skills/nis-stock-breakdown/scripts/build_chart_reel.py \
--ticker <T> --script output/setups/<T>/script.json
{
"hook": "spoken cover hook (1–3s, true to the data)",
"cover_line": "short hero line on the poster (≤5 words)",
"points": [
{"headline": "PROFITS", "number": "2×", "tag": "doubled, beat Wall St",
"signal": "spark", "color": "pos", "vo": "…plain why…"},
"×3 (NO 'RS' card)"
],
"breakout_vo": "…the trade in one line…", "invalidation_vo": "…", "cta_vo": "…",
"benchmark_vo": "…anchor it to NVDA/MSFT/AAPL/S&P from setup.json.benchmarks…"
}
Point card fields (each card is one white tile in the intro carousel — shown ONE
at a time, sliding in from the right):
headline — the bold accent word(s), left (e.g. "PROFITS", "BIG BUYERS"). UPPERCASE.
number — the hero, oversized mono, anchored RIGHT (e.g. "2×", "4", "+73%"). Omit if none.
tag — small mono pill under the headline (e.g. "doubled, beat Wall St"). Plain words.
signal — ONE micro-motion, the real-time-feed tell: "live" (pulsing ● LIVE — use
when actionable/buying now), "spark" (rising sparkline — growth/earnings), "arrow"
(↑ — up/expanding), "none". Pick the one that matches the fact; don't overuse "live".
color — accent: pos green · amber watch/trend · neg · info.
Legacy {big, label} still works (auto-split into headline/number) but prefer the explicit
fields. Keep headline short and let number carry the figure.
Anchor it to names everyone knows. setup.json now carries a benchmarks block —
the ticker's ~6-month return vs NVDA, MSFT, AAPL, and the S&P 500 (real, honest numbers).
The reel auto-inserts a "HOW IT STACKS UP" scene before the breakout: an animated bar
leaderboard, the ticker highlighted, sorted, negatives in red. Read benchmarks.returns and
write a tight benchmark_vo from the actual spread — e.g. "Six months: up forty percent.
The S&P did seven, Nvidia six, and Microsoft actually fell." Keep it honest: if the ticker
didn't beat NVDA, say "outpaced the S&P and most megacaps," not "beat everything." If
benchmarks is missing (FMP hiccup), the scene is skipped. You can also make one of the 3
cover points a benchmark stat ("+40%" / "6mo — beats the S&P") when that's the most
arresting fact.
The reel has three zones, and the VO across them follows the Hot Take Arc from
Step 3.5 — HOOK (Zone 1) → BUILD+PIVOT (Zone 2 + chart/volume scenes) → CLOSE (trade
scene) → CTA (outro):
ZONE 1: HOOK (0–3s) Stop the scroll. One verbal + one visual statement.
ZONE 2: VALUE DUMP (3–25s) Give the whole trade upfront. No withholding.
ZONE 3: PROOF LAYER (25–70s) Earn the value you already gave. Chart → volume → setup → breakout.
This structure is the opposite of most trading content, which builds to a reveal.
NIS reels lead with the reveal, then explain it. The viewer gets the trade in the
first 25 seconds. That's the retention mechanic.
The no-hedge rule applies to the VO exactly as it does to the caption. The hook
opens a loop; the pivot (chart + volume scenes) and close (trade scene) must resolve
it toward conviction. The ONLY place a caveat is spoken is Scene G (invalidation)
and the disclaimer — never in the hook, the walkthrough, or the trade close. If the
data forces "but it's not confirmed" earlier than Scene G, the ticker failed the
Step 1 gate; pick another. (Past losers RLJ/HOFT voiced their caveats mid-pitch and
the retention collapsed.)
ZONE 1: HOOK (0–3 seconds)
Write TWO parallel hooks — one verbal (what Hans says), one visual (what appears on screen).
They must reinforce each other but not duplicate each other.
Verbal hook — use one of these types (ranked by measured effectiveness for this
audience). The proven 🥇 winners did two things: they named what the viewer was
missing right now and they made the move feel imminent. ENVA's 345-view reel ran
Direct address + FOMO with a built-in clock ("one breakout away from a run — most
watchlists don't have it yet"); NWPX's 219-view reel ran a Specific number drop ("beat
by 59% — and held the move"). The strongest hooks fuse imminence with FOMO — a
specific number and a countdown ("RS 96, coiled at the pivot, days from breaking").
The bottom-tier hooks below are what the losers leaned on — prefer the top of this
list, pair it with a clean expectation-flip, and always put a clock on it:
| Type | Tier | Pattern | Example |
|---|
| Imminence / countdown | 🥇 proven | Put a clock on the setup — it breaks in days, not months | "This base is days from breaking — and the watchlists haven't caught it yet." |
| Direct address + FOMO | 🥇 proven | Name what they're missing RIGHT NOW | "If ENVA isn't on your watchlist, you're about to watch it from the sidelines." |
| Specific number drop | 🥇 proven | Lead with the most surprising number from setup.json | "Beat earnings by 59% last quarter — and held every point of it." |
| Stakes contradiction | 🥈 strong | "[What traders expect]. [What the chart shows instead]." | "Most traders are watching the wrong level on ENVA. Here's the one that matters." |
| Confession-flip | 🥈 strong | A belief the audience holds → what the data shows instead | "You probably think this kind of setup is rare. It screened clean on all 7 criteria." |
| Hard question | 🥉 ok | A question they're already asking themselves | "Is this a real breakout or a fake? Here's how to tell before it happens." |
| Abstract technical stat | ⚠️ weak | An isolated indicator value with no stakes | "2.9-to-1 up/down volume." (RLJ — flopped; it's a real NIS accumulation tell, but too abstract to open on — use it in the walkthrough, don't lead with it) |
| Turnaround / loss-to-profit | ⚠️ weak | "They were losing money, now they're not" | "Wall Street modeled a loss — it posted a profit." (HOFT — flopped; abstract + invites doubt) |
Avoid the ⚠️ tier. They correlate with the lowest-view reels: an abstract stat
gives a scroller nothing to feel, and a turnaround hook quietly admits the company was
broken, which invites skepticism instead of FOMO. If the only honest hook for a ticker
is a ⚠️ one, that's a signal the ticker failed the Step 1 conviction gate.
Rules for the verbal hook:
- Under 15 words
- No hedging: not "might", "could", "potentially"
- Contains either a specific number OR a named consequence
- Carries a clock — the breakout is imminent. The hook must make the viewer feel
the setup resolves in days, not months: coiled at the pivot, one volume day from
breaking, watchlists about to catch it. "Days from breaking", "before it moves", "by
next week", "you're early — but not for long". The urgency must be true (the name was
gated in Step 1 precisely because it's about to move); never fabricate a deadline.
- Written for spoken delivery — rhythm beats grammar
- Hans's voice: calm authority, not hype. He states facts like they're obvious.
Visual hook — what appears on screen in the first 3 seconds:
Do NOT open on the chart. The chart is mid-reel material. Open on ONE of:
| Visual hook type | What to show | Why it works |
|---|
| Number card | Single large number from setup.json (e.g. RS 94 or +127% EPS) on a dark background | Stops scroll; number demands context |
| The moment | A tight crop of the pivot candle with volume bar highlighted in amber — NO axes, NO labels | Ambiguity creates curiosity before the explanation |
| The trade card | Entry / Stop / Target / R:R on a single card — full trade visible in 3 seconds | Front-loads the value; viewer knows what they're getting |
| Split screen | Real breakout candle left / fake-out candle right, no caption | Creates instant question: "which one is this?" |
The visual hook plays for 2–3 seconds before the value card appears.
The verbal hook plays simultaneously — it is NOT a description of the visual.
Generate 3 ranked verbal hook options with hook type labeled. The agent picks the
best fit for the specific ticker's strongest signal.
ZONE 2: RAPID WALKTHROUGH (3–20 seconds)
Don't hold one dense card. Walk the setup as a burst of one-fact cards that hard-cut
every ~1.5 seconds — a single big number/label per card, a terse VO line per card.
This is still leading with value (the trade levels are in the burst); it's just paced
for retention. build_reel.py::walkthrough() builds these from setup.json; each is
its own scene. Typical order (cards drop when the data isn't there):
[TICKER] · WATCH "Northwest Pipe — a watch."
50·150·200 · stacked "Trend: stacked and rising."
52-WK · pressing high "Pressing its highs."
RS [rank] "Relative strength rank [rank]." (only if known)
[N]× · earnings beats "[N] straight earnings beats."
+[g]% · EPS growth "Earnings up [g] percent."
[udr]× · up/down vol "Volume says accumulation."
$[entry] · ENTRY "Entry, a break through [entry]."
$[stop] · STOP [risk]% "Stop, [stop]."
$[target] · TARGET 2:1 "Target, [target]. Two to one."
Each card is a clean stat card (big value + short label), one idea, no clutter. The
viewer gets the whole trade in the first ~15 seconds — as a rhythm, not a wall.
ZONE 3: PROOF LAYER (25–70 seconds)
Each scene has a narration line written FIRST, then the visual is chosen to match.
One line per scene. Hard cuts between scenes.
Scene order and narration guide:
Scene A — Setup (~5s)
Visual: Setup checklist graphic (Minervini gates, each line checking green)
VO: "Seven criteria. [TICKER] passed all of them. RS [rank], stacked MAs, [N] straight beats."
If RS rank unknown: "RS rank from the latest screen — in the top decile of the universe."
Scene B — The chart (~8s)
Visual: chart_bare.png — full candlestick chart with SMA lines and buy range band.
Apply a slow Ken-Burns push into the pivot area. Highlight the base with an amber
overlay during narration.
VO: "[N] weeks basing. [N] tight closes. Pivot at [pivot]. That coil is compressed energy."
Scene C — Volume (~7s)
Visual: Zoom into the volume bars section of the chart. Flash amber on bars where
volume ≥ 1.4× the 50-day avg.
VO: "[vol_ratio_today]x volume on the up days. [up_down_vol_ratio] up/down ratio.
Institutions are loading, not dumping."
Scene D — Fundamentals (~5s)
Visual: Earnings-beat bar chart from the slide deck (slide 5).
VO: "[N] straight earnings beats. EPS up [growth]% year-over-year. The business
is accelerating — the price just confirmed it."
Scene E — The trade (exact levels) (~8s)
Visual: Price ladder graphic — entry, stop, and target as horizontal lines on a
simplified chart. Animate the price moving from entry to target.
VO: "Buy on a confirmed close above [pivot] on volume. Stop below [stop].
Target [target_2r]. That's [R:R] on your risk. Size accordingly."
Scene F — Breakout animation (~20s, from animate_breakout.py)
Visual: The full real-vs-fake breakout animation rendered in Step 6.
VO during real breakout: "This is what a validated breakout looks like. Close above
the pivot. Volume confirms. The setup is live."
VO during fake-out: "This is the fake. Same pivot, light volume, closes back under.
That's your cue to stay out — or exit."
Scene G — Invalidation (~5s)
Visual: Simple text card on dark bg: "Setup fails if: [trigger 1] / [trigger 2]"
VO: "One rule: if it loses the [SMA50] on volume, the setup is done. No questions.
Exit the trade."
Scene H — Disclaimer + CTA (~5s)
Visual: Static dark card. Text: "Not financial advice. For education only."
Below: "newsimpactscreener.com"
VO: "Not financial advice. If you want the full screener that found this —
newsimpactscreener.com. Link in bio."
7B — Hook selection checklist
Before finalising the verbal hook, check:
If any box is unchecked, revise before passing to ElevenLabs.
7C — Render the reel (SILENT by default + a script.txt to voice yourself)
The cover IS the ad. build_chart_reel.py opens on a deliberate animated title card
— frame 1 must stop a muted scroll as a still, before any motion or voice registers.
Generator rules baked in:
- Identity, instant. Company logo (FMP, circle-cropped on a white disc) + the giant
$TICKER + company name + the hook line (cover_line) — all solid from frame 0, with
a soft amber glow breathing behind the ticker. This is the muted poster.
- A visual summary of the 3 facts. The reel's three points (from
script.json, else the
top 3 stat cards) render as rounded fact chips — big value + label + a colored accent
bar — that cascade in (staggered slide + fade over ~0.8 s). Motion that reads, not a
glitch. Taste-skill styling: radial-vignette depth, ONE amber accent, no clutter.
- Cover line comes from
script.cover_line, else --cover-line, else the --hook-style
default (tension → "one candle from triggering" / "breaking out — right now").
- (No chart, no projection "tease" on the cover — the chart is the body of the reel.)
After the cover, the chart grows through the real window and projects the validated
breakout to the 2R target, with the Hot-Take-Arc copy on floating cards.
No ElevenLabs by default. The reel renders silent and writes script.txt (VO per
scene with cut timestamps) so you record the voice yourself — a human VO / talking-head
massively out-performs TTS on TikTok, and native uploads beat API-posted ones. Pass --tts
to bake the ElevenLabs voice instead.
cd code/analytics
.venv/bin/python ../../.claude/skills/nis-stock-breakdown/scripts/build_chart_reel.py \
--ticker ENVA --script output/setups/ENVA/script.json [--hook-style tension|curiosity]
Green-screen / talking-head mode — the strongest hook fix is your face in frame 1.
--green-screen renders a clean cover (logo + ticker + hook, no cards baked in) and
exports the intro fact-cards as a transparent alpha video (intro_cards.mov, ProRes
4444) you drop over your talking-head intro. The cards play one at a time, each
spring-sliding in from the right, holding, then pushing the previous out left:
… build_chart_reel.py --ticker ENVA --script … --green-screen \
[--card-hold 1.1] [--card-exit slide|cut]
--card-hold = seconds each card holds (default 1.1); --card-exit cut = hard cuts (no
slide-out) so you trim transitions in CapCut. Without --green-screen, the cards are
baked into the cover as the same one-at-a-time carousel.
build_chart_reel.py conventions:
- 1080×1920 full vertical (Reels / TikTok / Shorts). Cards float in the top band so
the chart stays readable below and behind them.
- Silent by default (record VO to
script.txt's cut times). --tts uses
$ELEVENLABS_PRIMARY_VOICE_ID (Hans), override with --voice-id. --tempo 1.08.
In silent mode, scene durations are estimated from word count so the cuts land where a
natural read would.
- ONE chart for the whole reel: real display window (
--display-days, default 126 ≈ half a year) +
appended validated breakout projection (reuses animate_breakout.project()), so
the on-chart Entry/Stop/Target and the spoken levels come from a single live snapshot
and can't drift apart. (Climax is the validated breakout only — no fake-out here.)
- Growth timing is data-driven: the hook + rapid one-fact walkthrough grow the chart
through the real candles; the climax scene draws in the projection (price clears the
pivot on volume, runs to target); the invalidation + CTA cards hold the finished chart.
- Real vs. future is made unmistakable: during the real phase the chart is just solid
candles + SMAs (no entry/stop/target — those aren't history). The moment the projection
begins, a blue NOW divider drops, the future region tints blue with a vertical
PROJECTED label, the projected candles + volume render hollow + dashed, and the
entry/stop/target/buy-band appear only across the future region (the plan, not the
tape). SMAs never extend past NOW.
- As the projection starts, the camera snap-zooms in (eased push to the last
~
ZOOM_BARS real bars + the future, tightening both axes) over ~ZOOM_SECS (≈1s) — fast,
not a slow crawl — then holds while the breakout candles draw in. The real phase stays
full-frame.
- All cards (hook / one-fact stat cards / breakout / invalidation / CTA) are generated
from
setup.json + the live load — no per-ticker hand-editing. Each card fades in,
holds for its VO line, then hard-cuts.
- The visual hook is the hook card over the quietly-building chart — you are NOT
opening on a full static chart (it starts mostly hidden and draws in), so the
"don't open on the chart" rule from Zone 1 still holds in spirit.
- Requires only
setup.json + FMP creds (ElevenLabs only when --tts). Does not need
breakout_story.mp4 or the slide deck (it draws its own chart), so Step 6 is optional.
Target length: 40–70 seconds (it runs shorter than the legacy reel because the proof
lives in the one continuous chart, not in separate scenes).
Legacy: the stacked-card reel (build_reel.py). The earlier format — a sequence of
full-frame 1080×1350 scenes (hook visual → rapid stat cards → breakout animation →
proof tail). Use it when you specifically want the standalone real-vs-fake breakout
animation as the climax, or a 4:5 feed crop.
.venv/bin/python ../../.claude/skills/nis-stock-breakdown/scripts/build_reel.py \
--ticker ENVA --hook-text "[hook]" --hook-visual [number_card|moment_crop|trade_card|split_screen]
Step 8 — Write the post caption
Every reel ships with a caption. Save as output/setups/<TICKER>/caption.txt.
Caption rules:
- First 125 chars = the verbal hook from Zone 1. It must be self-contained and
scroll-stopping as text alone — this is what appears in the feed before "more".
- Body (3–5 short paragraphs): the setup in plain words → the trade (entry / stop /
target, R:R) → the real-vs-fake breakout warning → watch/swipe prompt.
- Exact numbers are fine in the caption.
- Close with
newsimpactscreener.com and a disclaimer line.
- Hashtags on the last line: 8–14. Mix broad (#swingtrading #stocks), the ticker
(#ENVA), and method (#breakout #relativestrength #momentum). No emojis.
- Anchor every claim in
setup.json. Never invent a level, a beat, or an RS rank.
Step 9 — Publish to Sanity as a blog post (optional)
Only do this when asked. Always write both body and cavemanBody (project rule).
- Type:
post. Fields: title, slug, author (ref), mainImage,
categories, publishedAt, body, cavemanBody.
- Field map: cover hook → title + intro; each slide →
h2 + paragraph; chart →
mainImage + inline in chart section; trade → its own section with levels; caption
→ closing CTA.
- Default to draft for human review. Use Sanity MCP for the document. Images upload
via assets API with
SANITY_TOKEN. Confirm dataset/attribution before writing.
Output format for the script (Step 7A)
Deliver the reel script in this shape before rendering:
# 🎬 Reel Script: [TICKER] — [one-line read]
**Status:** [actionable / watch / extended]
**Hook type:** [chosen type]
**Visual hook:** [number_card / moment_crop / trade_card / split_screen]
---
### HOOK VARIANTS (ranked)
🥇 [Verbal hook text]
Type: [type] — [why it works for this ticker specifically]
🥈 [Verbal hook text]
Type: [type] — [why it works]
🥉 [Verbal hook text]
Type: [type] — [why it works]
**Selected hook:** 🥇 (or justify if choosing lower)
---
### ZONE 1: VISUAL HOOK
[Describe exactly what appears on screen for 2–3s]
### ZONE 1: VERBAL HOOK (VO)
[Full narration line — the selected hook]
### ZONE 2: VALUE CARD (VO)
[Full narration line]
### ZONE 3: SCENE-BY-SCENE
**A — Setup** [~5s]
VO: [line]
Visual: [what's on screen]
**B — Chart** [~8s]
VO: [line]
Visual: [what's on screen]
**C — Volume** [~7s]
VO: [line]
Visual: [what's on screen]
**D — Fundamentals** [~5s]
VO: [line]
Visual: [what's on screen]
**E — Trade levels** [~8s]
VO: [line]
Visual: [what's on screen]
**F — Breakout animation** [~20s]
VO (real): [line]
VO (fake): [line]
**G — Invalidation** [~5s]
VO: [line]
Visual: [card text]
**H — CTA** [~5s]
VO: [line]
Visual: [card text]
---
**Estimated total:** [Ns]
What makes a reel good vs. generic
Hook:
- Specific number or named consequence in the first sentence
- Visual hook does not match the verbal hook (they reinforce, not duplicate)
- Opens a loop the viewer must close by watching
Value delivery:
- The full trade is on screen within 25 seconds
- Viewer gets something real whether they watch 5 seconds or 90 seconds
Proof layer:
- Each scene has ONE idea
- Numbers, not adjectives ("1.6x up/down volume" not "strong accumulation")
- The breakout animation earns its 20 seconds — it shows both outcomes, not just the win
Narration:
- Hans speaks like he already knows you know the basics — no explaining what a pivot is
- Short lines, hard cuts, no "and then", no "so basically"
- The disclaimer is not an apology — it's stated flatly and the CTA follows immediately
What to never do:
- Build to a reveal that the audience has to wait for (front-load instead)
- State the trade only in the caption and not the reel
- Open on the full chart (save it for Scene B)
- Use "potentially", "might", or "could be" in Hans's narration
- Invent an RS rank, price target, or fundamental you don't have in setup.json to create better content