Skip to content
Yuval Gerber
01AI / ML Trading Systems

Wave

An autonomous day-trading system that reads the whole market every minute — and makes its own AI earn the right to act.

Role
Solo — research, ML, engineering, design
Year
2026
Stack
Python 3.12 · asyncio + qasync · PyQt6 · scikit-learn · NumPy · Claude Haiku 4.5 (Anthropic API) · Alpaca API · SQLite (WAL) · pyqtgraph · Keychain + Touch ID · pytest · PyInstaller

A native macOS app that re-ranks every tradable US stock each minute, runs rule-based strategies through judges, a cost gate and a hard risk engine, and manages every position on its own asyncio task with a stop resting at the broker. A gradient-boosted model and a budget-capped Claude layer watch every decision — in shadow, until they earn more. Paper trading on Alpaca.

stocks re-ranked every minute
13,509

the full tradable US universe, 4:00–16:00 ET

lines of Python
63.8K

73 modules · 202 async functions

test functions
1,035

the build refuses to ship if they fail

paper trades
310

Alpaca paper · Aug 19 – Sep 24, 2026

The idea

Every minute, Wave reads the entire US stock market. Almost nothing it sees becomes a trade — and that is the point.

01Overview

Paper trading on Alpaca · live trading is locked in code · v1.0.0

A trading desk in one native app.

Wave scans every tradable US stock, ranks the ones that are moving for a reason, and lets rule-based strategies propose trades. Each proposal then has to survive a gauntlet: day-type and efficacy judges, a Claude veto on chased entries, a cost gate that demands the expected move beat fees and slippage three times over, and a hard risk engine.

What survives becomes a bracket order with a stop resting at the broker from the moment of fill, managed by its own asyncio task. Every decision — taken or refused — is journaled to SQLite, and that journal is what the machine-learning layer learns from overnight.

02Architecture

Thousands of signals in. A few protected orders out.

Every refusal is logged with its reason, so the system can be audited — and so the model has something honest to learn from.

  1. Market feeds

    SIP · news · EDGAR

  2. Scanner 2.0

    13,509 · every 60 s

  3. The menu

    top 20 names

  4. Strategies

    ORB · Gap · VWAP · FPB

  5. Judges & veto

    day · efficacy · Claude

  6. TradeGate & risk

    costs × 3 · 1% risk

  7. Order

    broker-side stop

03The scanner

Wave’s mind: the whole market, every minute.

Scanner 2.0 re-ranks every tradable symbol on time-anchored relative volume — today’s volume against the stock’s own 20-day average at the same minute — boosted by fresh news, earnings and event leaderboards, and capped so leveraged ETFs can’t crowd the menu. In the app, each star is one scanned symbol.

PAPERLIVE
Wave’s mindHEURISTIC RANKER· reading the day…
▲DEMO CORP SURGES ON RECORD EARNINGS BEAT▼DEMO INDUSTRIES PLUNGES AFTER GUIDANCE CUT▲SAMPLE BIO WINS FDA APPROVAL▼PLACEHOLDER INC ANNOUNCES SHARE OFFERING▲EXAMPLE TECH SIGNS MULTI-YEAR CONTRACT▲DEMO CORP SURGES ON RECORD EARNINGS BEAT▼DEMO INDUSTRIES PLUNGES AFTER GUIDANCE CUT▲SAMPLE BIO WINS FDA APPROVAL▼PLACEHOLDER INC ANNOUNCES SHARE OFFERING▲EXAMPLE TECH SIGNS MULTI-YEAR CONTRACT
candidate rejected acceptedone star per scanned symbol

04Decision engine

Rules decide direction. Everything else decides whether.

05Machine learning

A model that has to earn its way in.

A scikit-learn gradient-boosting ensemble scores every candidate and writes its verdict to the journal — in shadow mode, with zero influence on trading. Each night it relabels the day’s scanner snapshots and retrains. The switch to ACTIVE exists in the app, and stays disabled until the model has proven itself.

PAPERLIVE

Brain

SHADOW — the Brain watches every candidate and writes its verdict in the journal. Zero influence on trading.

OFFSHADOWACTIVE
BrainLadderDataModels
CANDIDATEPRICE & VOLUMEMARKET CONTEXTPATTERN MEMORYVERDICT
36,790

LESSONS LEARNED

3,001

WINNING LESSONS

1,100

LOSING LESSONS

SHADOW

STATE

06ML engineering

Built not to fool itself.

07LLM layer

Claude in the loop — on a budget.

Claude Haiku 4.5, called over plain HTTPS with strict JSON outputs, hard spending caps and a cache — never allowed to invent a trade.

08Exits

Every position gets its own brain — and a stop at the broker.

A per-second exit state machine measures how stretched price is from VWAP in units of the stock’s own volatility, switches between three “gears”, caps giveback and banks volume-burst climaxes. Behind it sits a classic 7-layer ATR exit engine — and the broker-side stop that never stands down. Stops only ever ratchet tighter.

Illustration of Wave’s exit logicA price path rises above VWAP while a protective stop ratchets upward and never loosens; a volume burst marks a climax and the position exits near the top.entry · bracket stop rests at the brokerstop ratchets up — never loosensclimax: 15-s volume ≥ 3× pacebankILLUSTRATIVE — NOT A REAL TRADE
price session VWAP protective stop volume burst

09Results

Paper-traded, journaled, rebuilt from fills.

Wave trades an Alpaca paper account, and its P&L is rebuilt trade by trade from the broker’s own fill records. Going live is gated by a written checklist — at least 30 sessions, 200 trades, a profit factor of 1.3 and zero unreconciled incidents.

+1,010.92 $ (+1.02%)

all time · Alpaca paper account

1D1W1M3MALL
202530Sep04091419×7×6×3×5×4×5×6×5

Recreated from Wave’s Performance tab. Paper trading — past results say nothing about future ones.

10Interface

A native cockpit — and a Telegram bridge.

A PyQt6 app with Apple-style liquid glass and seven tabs: live position cards, a stepped equity curve, the scanner galaxy, the ML brain, system health with a market-clock ring, a searchable log and Touch-ID-protected settings. From the phone, an owner-only Telegram bridge reports and pauses — dangerous commands need a one-time code.

PAPERLIVE

CONNECTIONS

  • Alpaca connected (paper)
  • Data feed — SIP stream live
  • Telegram bridge polling
  • DB wave_paper.db — schema v14

MARKET CLOCK

--:--:--

market opens in

09:30 ET · NYSE

RISK & LIMITS

no halts · entries flowing

1% / trade · 3% day · 6% week · ≤25% notional · ≤0.5% of daily volume

Kill switch

DATA FEED HEARTBEATS

trades 0s ago

quotes 0s ago

bars 0s ago

NEWS BRAIN (LLM)

Claude Haiku 4.5 · first-print headlines only

cap $0.25 / day · 400 calls

FEE SCHEDULE IN FORCE

finra_taf $0.000195 / share sold

sec_section31 $20.6 per $1M sold

11In numbers

Python modules
73

181 classes, 1,338 functions

SQLite tables
25

14 numbered migrations, WAL mode

functions type-annotated
97%

ruff with security rules on

pipeline stages
11

feeds to dashboard, all journaled

12From the source

Fail closed, by construction.

waveapp/engine/brain.pyview on GitHub ↗
golden_x = np.asarray(bundle["golden_x"], dtype=np.float64)
golden_p = np.asarray(bundle["golden_p"], dtype=np.float64)
got = brain.score_raw_matrix(golden_x)
if got.shape != golden_p.shape or np.max(np.abs(got - golden_p)) > GOLDEN_TOLERANCE:
    logger.error(
        "BRAIN REFUSED: golden-vector self-test failed (max diff %.3g) — rules-only",
        float(np.max(np.abs(got - golden_p))) if got.shape == golden_p.shape else -1.0,
    )
    return None
waveapp/engine/brain_nightly.pyview on GitHub ↗
for fold_days in folds:
    if len(fold_days) == 0:
        continue
    fold_set = set(fold_days.tolist())
    lo_i = days.index(fold_days[0])
    hi_i = days.index(fold_days[-1])
    embargo = set(days[max(0, lo_i - EMBARGO_DAYS) : lo_i]) | set(
        days[hi_i + 1 : hi_i + 1 + EMBARGO_DAYS]
    )
    train_mask = np.array([d not in fold_set | embargo for d in day_arr])
    test_mask = np.array([d in fold_set for d in day_arr])

Wave is a personal project that trades a paper (simulated) brokerage account; live trading is locked in code. It is not a product, a service or financial advice, and past results do not predict future ones.

Next project

MiniGPT Studio