Chart-Reading Trainer

Chart-Reading Trainer

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.


Open it

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.


How to use it

  1. Read the frozen chart — price line, a full MACD (12,26,9) panel (blue MACD line + yellow signal line + histogram bars, same as Robinhood’s), momentum(12), volume bars, and the exact readout numbers up top (ticker · date · time in MST · price · MACD · sig · hist · momentum), plus a time axis along the bottom. The lines matter because you can watch them converging — the early tell that a cross is approaching — before the histogram flip prints.
  2. Call the next 30 minutes: 📉 DOWN (short) · ➡️ HOLD (chop) · 📈 UP (long). Click a button, or use the keyboard: down, up, hold (sideways), Enter = next setup.
  3. It reveals the next 6 bars (30 min), tells you right/wrong, and shows a one-line “what the read leaned” hint (e.g. “MACD green, momentum +2.1 (rising) → the read leaned UP”) so you connect the indicators to the outcome.
  4. It tracks Accuracy · Record · Streak · Best across the session.

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.)


Matching a setup back to Robinhood

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

  1. Note the ticker + date + decision-bar time (e.g. SPY, 2026-06-11, 11:55 AM).
  2. On Robinhood: open that ticker, set the 5-minute chart, scroll to that date.
  3. Find that time — the setup is every bar up to it (the “what you see” side); the reveal is the 30 min after. The time axis along the bottom of the trainer chart lets you line the whole ~2h40m window up bar-for-bar.

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.


Order-Ticket Trainer (order mechanics practice)

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):

  • Quantity defaults to 1 — a protective stop covering less than the position logs a fat-finger warning.
  • Sell-stop at/above market → REJECTED with RH’s message (the typing-race).
  • Stop-limit buffer on the wrong side → REJECTED (“stop LOW, limit HIGH” on buys).
  • Marketable limits fill instantly ("$144 or better" when the ask is cheaper — the “future buy” mistake).
  • Stops gapped through fill at the gap, not at your price; stop-limits gapped past their cap don’t fill at all (and tell you).
  • Insufficient buying power → REJECTED (the margin-desk “no”).
  • 🛡 SEATBELT indicator — green only when a resting stop covers the FULL position; flashes red with the unprotected share count otherwise. The single most important habit: never let it flash.

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.)


Audio drill (eyes-free)

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.


Files

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.

Training tips

  • Do 20–30 reps a sitting. Watch your accuracy climb over a few sessions.
  • Don’t chase 100%. Even strong traders are right ~55–60% on raw direction — the edge is in sizing and stops, not being psychic. This trains the read; the money management is the Tips & Tricks discipline.
  • Read the hint every time. Over 100 reps you’ll feel which MACD + momentum combos actually predict the move and which are noise.
  • Respect the HOLD calls. A big share of setups are chop. Calling HOLD instead of forcing a direction is the Day-3 lesson in a safe sandbox.

The generator script (full source)

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.