A risk-free flashcard trainer for building chart-reading instinct — the exact skill used when hand-trading (reading MACD histogram + momentum + volume + price and deciding a direction). Built 2026-07-17 after the third manual session, to practice the read without risking a dollar or a single forgotten stop.
It freezes a real historical setup at a decision bar, you call UP / DOWN / HOLD for the next 30 minutes, and it reveals what actually happened, scores you, and explains what the indicators leaned. Do hundreds of reps and the pattern-read becomes muscle memory.
It’s a single self-contained HTML file (all data baked in, works offline):
/home/aztechguy/Projects/chart_trainer/trainer.html
Just double-click it (or open it in any browser). No server, no internet needed.
↓ down, ↑ up, → hold (sideways), Enter = next setup.Scoring rule: over the next 30 min, if price moves > +0.4% the answer is UP; < −0.4% is DOWN; anything in between is HOLD (chop). The HOLD button is deliberately front-and- center — learning when NOT to trade an ambiguous setup is half the skill.
Tickers: a dropdown switches between All (mixed) · SOXL · QQQ · SPY. “All” mixes setups from all three for maximum variety; picking one focuses your reps on that name.
Anti-memorization (by design): ~60 days × 3 tickers × ~50 decision points each = ~7,000 possible setups, drawn at random, and the session tracks what you’ve already seen and never repeats it (changing ticker or resetting clears the seen-list). So you’re always reading the chart, never recognizing it — there’s nothing to memorize your way through.
Drill your misses (Review deck): every setup you call wrong is saved to a Review deck. Click 🔁 Review wrong (N) to replay only those — the ones that actually fooled you. Nail one and it graduates off the deck; miss it again and it stays for another pass. Clearing the deck means the pattern has clicked. Review reps don’t count toward your main Accuracy/Record — it’s practice, not a test — so the scoreboard stays honest, and the deck count shrinking is your progress bar. This is deliberate spaced-repetition: you re-see your own mistakes until the reason becomes muscle memory.
♻️ Reset session wipes the score, streak, seen-list, and Review deck to start completely fresh. (The Review deck lives in the current session — reloading the page clears it — so it’s for drilling within a sitting.)
Every setup is real, timestamped data, so you can pull it up on Robinhood and study the whole move (not just the 30-min reveal) — an excellent way to audit your read against reality. The readout gives you the exact coordinates:
SPY · 2026-06-11 · 11:55 AM MST · $735.76 · MACD hist -0.01 · Momentum +0.80
Timezone: all trainer times are America/Phoenix (MST) — the same clock Robinhood shows in Arizona, so they match 1:1 with no adjustment. (Arizona skips daylight saving, so it’s MST year-round; in summer that happens to equal Pacific/PDT on the clock, which is why it can feel like “PST” — but it still lines up with RH’s MST either way.)
One quirk: charts start ~1 hour into the session (earliest bar ~7:30 AM MST, not the 6:30 open), because the first 12 bars of each day get consumed computing Momentum(12). Every bar shown still carries a correct, matchable timestamp.
Built 2026-07-20 after a live session with six order-entry fumbles (stop-price races, Qty=1 fat-fingers ×2, a limit-vs-stop mixup, and an unnoticed failed stop that cost −$26.55). It’s a flight simulator for the Robinhood order ticket: real SOXL 5-min bars (last ~20 days) replayed bar by bar, a $2,000 paper balance, Buy / Sell-Short, and every order type — Market, Limit, Stop market, Stop limit, Trailing stop — with realistic fills AND rejections.
Open: /home/aztechguy/Projects/chart_trainer/order_sim.html (double-click, offline).
Deliberately preserved traps (each one was paid for live on 2026-07-20):
Day ends → auto-flatten at the close, W/L + P&L score. Space steps a bar, P plays.
Refresh data: run build_order_sim.py with the same venv as the chart trainer.
The data ages (it’s the last ~60 days at build time). To roll it forward to the newest 60 days for all three tickers, re-run the generator:
/home/aztechguy/.gemini/antigravity-cli/brain/23d9e61b-4566-4487-9840-0d0b4ef78445/.venv/bin/python3 \
/home/aztechguy/Projects/chart_trainer/build_trainer.py
It fetches SOXL/QQQ/SPY 5-min history via yfinance, recomputes the indicators, and overwrites
trainer.html. (Any python with yfinance + pandas works; the path above is the venv that
already has them.)
A spoken version of the trainer for reps away from the screen (commute, walk, workout). For each setup it reads the ticker, time, price, and indicator numbers, then narrates the shape in plain English — trend, where price sits in its range, momentum state, histogram, and MACD-line position — so you still practice the trend-and-extension read without hearing the answer. Then it goes silent ~10 seconds for you to call it out loud, and finally reveals what actually happened plus the morning trend.
Honest limit: audio can’t show the chart, so it trains the numbers + spoken-reasoning half of the skill, not the visual pattern. Use it as a supplement to the visual trainer — its bonus is that it forces you to say the context read out loud.
Generate a file (isolated .venv-audio venv, natural Microsoft neural voice, no API key,
plays on any phone):
/home/aztechguy/Projects/chart_trainer/.venv-audio/bin/python3 \
/home/aztechguy/Projects/chart_trainer/audio_trainer.py --n 10 --pause 10 --ticker ALL
Writes trainer_audio.wav next to the script. Options: --n setups · --pause seconds of
silence · --ticker ALL|SOXL|QQQ|SPY · --voice (default en-US-AriaNeural; try
en-US-GuyNeural for a male voice) · --out path. It reuses the data already embedded in
trainer.html (no re-fetch), so refresh trainer.html first if you want newer setups. Each
setup runs ~40–60s, so --n 10 is roughly a 7–9 minute file.
| File | What it is |
|---|---|
/home/aztechguy/Projects/chart_trainer/trainer.html |
The trainer (generated, ~750 KB, self-contained). This is the thing you open. |
/home/aztechguy/Projects/chart_trainer/build_trainer.py |
The generator — fetches 60 days of data and writes the HTML. Canonical source; full listing below. |
/home/aztechguy/Projects/chart_trainer/audio_trainer.py |
The audio-drill generator (eyes-free spoken version). Runs in the .venv-audio venv; reads trainer.html’s data and writes trainer_audio.wav. |
Canonical file: /home/aztechguy/Projects/chart_trainer/build_trainer.py. Re-run it to
refresh the data; edit TICKERS to add/remove symbols.
#!/usr/bin/env python3
"""Chart-reading TRAINER generator -- SOXL + QQQ + SPY.
Fetches ~60 days of real 5-min history for each ticker, computes the same
indicators read live (MACD histogram + momentum(12) + volume), and writes one
self-contained `trainer.html` you double-click to open. It freezes a random
historical setup at a decision bar; you call UP / DOWN / HOLD for the next
30 min; it reveals what actually happened and scores you. No money -- all reps.
Anti-memorization: setups are drawn at random across 3 tickers x ~60 days x
~50 decision points each (~7,000 possible setups), and the session tracks what
you've already seen and never repeats it -- so you're always *reading*, never
*recognizing*. Re-run this any time to roll the data forward to the latest 60 days.
"""
import json
import pandas as pd
import yfinance as yf
TICKERS = ["SOXL", "QQQ", "SPY"]
OUT = "/home/aztechguy/Projects/chart_trainer/trainer.html"
def build_days():
out = []
for tk in TICKERS:
df = yf.Ticker(tk).history(period="60d", interval="5m")
if df.empty:
print(f" {tk}: no data"); continue
c = df["Close"].dropna()
vol = df["Volume"].reindex(c.index).fillna(0)
ef = c.ewm(span=12, adjust=False).mean(); es = c.ewm(span=26, adjust=False).mean()
macd = ef - es; sig = macd.ewm(span=9, adjust=False).mean(); hist = macd - sig
mom = c - c.shift(12)
frame = pd.DataFrame({"c": c, "v": vol, "h": hist, "m": mom}).dropna()
frame["day"] = frame.index.date
n = 0
for day, g in frame.groupby("day"):
if len(g) < 45:
continue
bars = [{"c": round(float(r.c), 2), "v": int(r.v),
"h": round(float(r.h), 3), "m": round(float(r.m), 2)}
for r in g.itertuples()]
out.append({"tk": tk, "date": str(day), "bars": bars}); n += 1
print(f" {tk}: {n} days")
return out
# HTML is a template string with __DATA__ replaced by the JSON of all days. The UI is a
# vanilla-canvas renderer (3 panels: price+volume, MACD histogram, momentum) plus the
# guess/reveal/score loop, ticker selector, and no-repeat setup picker. Full markup lives
# in the canonical .py file; it is a single r"""...""" block written verbatim to trainer.html.
HTML = r'''<!DOCTYPE html> ... (self-contained page: canvas chart + UP/DOWN/HOLD buttons +
scoring + ticker <select> + no-repeat pick) ... ''' # see canonical file for full markup
def main():
days = build_days()
if not days:
print("No data fetched."); return
html = HTML.replace("__DATA__", json.dumps(days))
with open(OUT, "w") as f:
f.write(html)
nbars = sum(len(d["bars"]) for d in days)
print(f"Wrote {OUT}")
print(f" {len(days)} trading days across {len(TICKERS)} tickers, {nbars} bars")
if __name__ == "__main__":
main()
The
HTML = r'''...'''block above is abbreviated here (it’s ~120 lines of canvas/JS UI). The full, runnable version is the canonical file on disk — this KB page documents what it does and how to refresh it, and the data-pull logic (build_days()) is shown in full.