Skip to content

About

Algorithmic trading research and execution platform for futures — test a strategy against real market data bar by bar, replay the tape as it happened, and follow it live.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

AutoTrader — Automate · Analyze · Execute · Succeed



AutoTrader is a futures research platform: it reads the market, tests an idea against it bar by bar, charges realistic costs for every fill, and follows the live tape while it does. Built at WealthWise Advisors.


AutoTrader overview: a futures trading research platform. Market data, prepare, analyze, test strategy, simulate trades, results. Built with Python, FastAPI, React, TypeScript, Vite, pandas, Tailwind, Docker, AWS EC2 and Schwab. CI passing, deployed live, 2,709 tests passing, 83.2% coverage, proprietary licence.

Market data flows through resampling, analysis, strategy and the paper broker to a scored result


👀 Quick Look

The analysis engine on real ES data — price with swing structure and EMAs, then RSI(2), StochRSI and RSI(13) beneath it, all on one shared time axis. Entries, exits and every labelled swing are drawn by the same code the backtest scored.

The AutoTrader price chart: ES candles with labelled ZigZag swings, EMA9 and EMA21, VWAP bands, the Volume Profile with its POC and value area, long and short entry markers, above RSI(2), StochRSI and RSI(13) panels sharing one time axis

2,709 83.2% 3.12 5
tests passing coverage Python strategies

Verified by the runners, not typed from memory — see Testing & Quality.



📑 Table of Contents

◆ Understanding it

◆ How it works

◆ Working with it



🎯 About the Platform

➜ Most trading tools tell you what to buy. This one tells you whether you should have believed the last thing that told you what to buy.

➜ AutoTrader is an instrument for measuring trading ideas. You give it an instrument, a date range and a strategy; it replays the market bar by bar, executes the strategy against a broker that charges commission and slippage on every fill, and hands back a result you can reproduce exactly.

  Read the market

➜ Elliott Wave structure

➜ Swing pivots

➜ Chart and candlestick patterns

➜ VWAP with deviation bands

➜ Volume profile

➜ A regime classifier that says whether the market is trending, ranging or volatile

  Test the idea

➜ Bar-by-bar replay with a paper broker

➜ Commission per contract

➜ Slippage in ticks

➜ Prices rounded to the real increment — a plausible fill, not a closing price

  Trust the number

➜ 2,709 tests

➜ Deterministic runs

➜ One shared bar aggregator

➜ A deploy that refuses to succeed unless the server is actually running the commit it claims



🧭 Who This Is For

◆ If you trade and want to check an idea;

  1. Start with the Quickstart.

  2. Point it at synthetic data.

  3. Run a strategy end to end without an account anywhere.

  4. Then read Backtesting & Execution for what the fills actually cost you.

◆ If you build software and want to read the engine;

  1. Start with src.

  2. src is the whole engine and imports nothing from api or web.

  3. It runs from a test, a script or a server unchanged.

  4. Architecture explains the seams.

  5. analysis is where the market reading lives.

Note

Three ways in, depending on how much time you have;

  1. Five minutes: Quick Look and Why It Exists, the four bugs that shaped it.

  2. An hour: Quickstart and a real backtest.

  3. A day: docs carries the product requirements, the requirements spec and the Elliott Wave rule inventory.



💡 Why It Exists

➜ A backtest is easy to write and very easy to fool yourself with. Four specific ways, each one a real bug this codebase has hit and fixed, shape how the platform is built:

1

➜ A bar that changes after you have seen it. A bar polled mid-minute has a high and a low that are still moving. Show it, and every number derived from it is provisional without saying so.

➜ Only closed bars ever reach the tape.

2

➜ Two code paths that disagree about a bar. Aggregation was once duplicated across three providers; the copy that forgot to anchor to the session kept reintroducing shifted bars.

➜ One aggregator, in resample.py.

3

➜ A number that moves when you touch a checkbox. The same 30-minute bar reported two different VWAPs depending on which other timeframes were selected.

➜ Each bar carries its own volume-weighted price, built from the minutes inside it.

4

➜ A deploy that quietly changes nothing.

➜ The pipeline asks the running server which commit it is serving and fails the run unless it matches.



🔀 What Makes It Different

Four claims a research tool can make, and what each one costs to actually mean. Every row points at the code that enforces it, because a claim you cannot check is a slogan.

  A fill you can defend

Orders fill at the next bar's open, plus slippage in ticks, rounded to the instrument's real increment — never the signal bar's close

Enforced in paper_broker.py

  One definition of a bar

A single aggregator, session-anchored, shared by the historical and the live path — so a 5m bar cannot mean two things

Enforced in resample.py

  A number that stays put

A session grown bar by bar must come out byte-identical to one handed all the data at once

Enforced in tests

  A deploy that cannot lie

The pipeline asks the running server which commit it is serving and fails unless it matches

Enforced in deploy.yml

Note

None of these are hard to say. They are all annoying to do, which is why the four bugs above exist — every one of them is a case where the easy version was already shipped and quietly wrong.



✨ Highlights

📡 Follows the live market

  1. Load today, press Play, and the tape keeps up on its own.

  2. New bars land within ~15 seconds of closing.

  3. No reloading, no checkbox to remember.

🌊 Elliott Wave engine

  1. Thirteen modules — impulses, corrections, diagonals, triangles, combinations.

  2. Explicit rules a count must satisfy.

  3. A hierarchy pass that nests degrees.

⏱️ Fifteen timeframes, one clock

  1. 1m through 1w. The thirteen intraday ones replay in the Market Grid, where panes advance off a shared market clock measured in market time, not in bars.

  2. So a 1m pane and a 1h pane always show the same instant.

📊 Broker-accurate VWAP

  1. Volume-weighted price per bar with σ bands at any level.

  2. Colour-grouped by whole number so agreement across timeframes is visible at a glance.

💰 Costs that are actually charged

  1. Commission per contract.

  2. Slippage in ticks.

  3. Tick-size rounding.

  4. Per-instrument contract specifications so P&L lands in real currency.

📤 Exports that match the screen

  1. CSV.

  2. XLSX.

  3. PDF.

  4. DOCX.

  5. With charts rendered server-side from the same data the dashboard drew.



📡 Live Replay

Candles arriving on their own as the market prints them, with VWAP and its two-sigma bands, the forming bar withheld, and the polling loop that decides when the tape advances

  • ➜ The flagship surface — loads history, plays it bar by bar, then keeps going
  • ➜ Follows the live market — instead of stopping at the snapshot it loaded
  • ➜ The forming bar is withheld — the tape advances only on a bar that has closed

What makes it trustworthy rather than merely live

Rule Why
Only closed bars A bar polled mid-minute is still moving. Showing it puts a number on screen that changes afterwards — so it waits for the close
Never silent The status line always states what is happening: 1 new bar, now at 15:56 · the newest bar is still forming · could not reach the data source. A quiet screen during an outage looks exactly like a calm market
A pause is respected Following keeps the tape at the live edge; it does not drag you back from a bar you paused to study
The gap is named "1 min behind the clock — the current bar is still forming." And when the gap exceeds what the design accounts for, it says the feed may be delayed instead of reassuring you

Note

Why it looks one bar behind a broker platform. thinkorswim draws the bar currently forming; this app draws the last bar that closed. Same instant, different bar. That is a difference in what is displayed, not a delay — and measured against the live feed, a closed bar reaches the tape in 5–16 seconds.



🏗 Architecture

Data sources feed one core engine, which the interfaces read; the engine imports nothing from them
  • ➜ The engine knows nothing about how it is called — no HTTP, no React, no framework inside src
  • ➜ Same answer from anywhere — a test, the API and a script all run the identical analysis

Key points

  • Engine does not care where bars come from, or how they arrive
  • Single analysis path for all sources
  • Deterministic replay with shared clock

Data flow

  • Sources → Core Engine → Interfaces
  • Replay Engine feeds back into Strategies
  • Same answer every time

Design principles

  • One shared aggregator (Resampler)
  • Event-driven, modular components
  • No HTTP, no React, no framework inside src/


🔄 Project Workflow

How a change actually travels from an idea to the live URL.

A change travels from idea through research, implementation, verification, backtest, pull request and merge to a deploy whose commit is asserted
StageWhat has to be true to move on
ResearchThe problem is measured, not assumed. A reported "2-minute lag" was sampled nine times against the live feed before a line of code changed — which showed the provider was not the cause
ImplementA test exists that fails against the previous commit. A test that passes either way defends nothing
Verify2,227 Python tests, 492 web tests, ruff, and npm run build — not tsc --noEmit, which reports success on broken JSX
DeployThe running server is asked which commit it serves. Mismatch fails the run


📌 Project Status

CI Deploy Tests Coverage

License

SignalWhat it actually measures
CIThe workflow's own conclusion on master — lint, type check, unit tests, security audit, frontend tests and a package build
DeployLive at 3-218-23-37.sslip.io. The deploy asserts the commit answering on port 80 equals github.sha, so a green tick cannot hide a stale build
Tests2,709 passing — 2,217 of 2,227 Python (10 skipped) and 492 web. Counted from pytest and vitest, not estimated
CoverageLine coverage of src and api, from the TOTAL row of pytest --cov=src --cov=api. The CI gate is 70%
LicenseProprietary — see LICENSE


🔍 Market Intelligence

An Elliott Wave impulse drawing itself wave by wave, then the ABC correction, with each rule checked as it completes

🌊 Elliott Wave

elliott_wave

📐 Price structure

analysis

Important

validation is the module that matters most. Without it, a pattern search finds a "wave count" in any random walk you hand it.

VWAP the way a broker platform computes it. Each bar carries its own volume-weighted price, built from the minutes inside it, rather than (H+L+C)/3. Measured on the same closed 30-minute bar:

Selection Source VWAP
30m alone 30m 7810.2336
1m + 30m 1m 7809.9279 ← matches the reference platform

Same bar, two answers, decided by a checkbox — which is exactly the class of non-determinism the shared aggregator exists to kill.



🧠 Strategy Engine

  • ➜ Five strategies ship, all behind one interface — base_strategy.py
  • ➜ Any of them drops into any run — the engine never knows which it holds
Strategy The idea
🔀 rsi_divergence Price makes a new extreme, RSI does not — momentum failing to confirm
↩️ rsi_mean_reversion Fade an exhausted move back toward the mean
📈 ma_crossover Fast and slow moving averages crossing
🚀 breakout Range resolution on expanding volume
🎛️ regime_adaptive Switches behaviour on the regime classifier's reading
Price prints a lower low while RSI prints a higher low; the divergence arms the setup, and a later close above the divergence bar's high is the entry

Each timeframe gets its own strategy instance and its own paper broker.

  • ➜ The trigger is the divergence bar's high — a later close above it is the entry
  • ➜ The divergence only ARMS the setup — several bars can pass before the trigger is taken
  • ➜ Strategies are stateful — a setup armed on one bar is confirmed several bars later
  • ➜ So one shared instance would corrupt them — an hourly bar would overwrite the one-minute strategy's pre-conditions and silently change its signals


⚙️ Backtesting & Execution

Bars produce a strategy signal, the paper broker fills it with slippage and commission, and the position becomes P&L and metrics
  • ➜ Fills are charged, not assumed — commission per contract, slippage in ticks, prices rounded to the instrument's real increment
  • ➜ Contract specifications per instrument — tick size, tick value and point value for ES, NQ, MES, CL and others, so P&L lands in real currency
  • ➜ Session-aware — RTH, Globex (18:00–17:00) or 24-hour, entered in ET, CT, MT or PT and always stored as Eastern, with VWAP anchored to the session open rather than to midnight. Intraday only: a 1d or 1w bar already is a session, so it skips the filter and carries no VWAP
  • ➜ Deterministic — the same inputs produce the same output every time, which is what makes a rebuilt result comparable to the one it replaced

◆ One clock, thirteen timeframes

➜ step() advances one bar — and a bar is a different span on every timeframe

➜ Stepping N engines per tick desynchronises them: after 100 ticks a 1m pane has moved 100 minutes, a 1h pane 100 hours

➜ So the clock runs in market time — one tick advances exactly one base bar

➜ Every other timeframe steps only when its own bar has closed

➜ Every lane reaches the right edge together — that is what synchronised means here

➜ multi_replay.py


One playhead crosses sixty minutes of market time; the 1m lane closes sixty bars, 5m closes twelve, 15m closes four and 1h closes once — and every lane reaches the right edge together


🗄️ Database

SQLite — one file, no server, no port, no password, no monthly bill.

◆ In SQL

➜ Every scalar metric

➜ One row per trade

➜ 6 indexes — symbol, date, Sharpe, strategy

➜ Trades cascade on delete

◆ Beside it, as Parquet

➜ Equity curve

➜ OHLCV frame

➜ dtypes and DatetimeIndex preserved

➜ Row counts recorded, so truncation shows

File autotrader.db — survives restarts and redeploys
Schema schema.sql — backtests, trades
Code db — routers never see a cursor
Guard A test fails the build if a metric has no column

Query across runs:

store.summaries(symbol="ES", min_sharpe=1.5, since="2026-03-01")

Note

Writes fail soft — a locked file logs and serves from cache. If results stop surviving restarts, grep for could not persist backtest.



📊 Dashboard

A React 19 single-page application over the FastAPI backend.

Page What it does
⚡ Live Replay Multi-timeframe grid, consolidated tape, playback controls, live following
📉 Backtest Configure, run and read a scored result
📤 Export Data Pull bars out for use elsewhere
  • ➜ Charts — candles with VWAP, deviation bands, volume profile and wave overlays
  • ➜ Deviation colouring — band values grouped by whole number, with disjoint palettes for upper and lower so the two can never be confused
  • ➜ Logic lives in lib — 427 of the web suite's 492 tests run without a DOM


🔐 Accounts & Access

Open registration. Four ways in, or a password.

◆ Ways to sign in

  • Google — one click, address already verified
  • GitHub — one click, verified address read from /user/emails
  • LinkedIn — one click, OpenID Connect
  • Twitter / X — asks for an address on first use, then one click
  • Username + password — argon2id, 12 characters minimum

◆ Getting back in

  • Forgot password — single-use link, expires in 1 hour
  • Forgot username — emailed to the address on the account
  • Completing a reset revokes every existing session
  • Both refuse to say whether an address has an account
What an account does not grant — the broker stays out of reach

There is exactly one Schwab connection: one credentials.yaml, one schwab_tokens.json, no per-user notion anywhere in schwab_provider.py. It is the operator's own brokerage authorisation, not the application's.

So it is not a permission an account can earn — not by registering, and not by verifying an email, because verifying an inbox says nothing about whether someone should reach somebody else's broker.

Control Effect
users.is_owner defaults to 0 Every account created by any route starts without it
No parameter on create_user No argument any endpoint could pass to grant it
Set only by manage_users.py Requires shell access on the server
Per-user isolation — schema v4, and why the cache mattered too

backtests and trades carry a user_id. Every read, write and delete filters on it, and no repository function takes a default — a forgotten argument is a TypeError, not a leak.

Scoping the SQL alone was not enough. store.py held an in-memory cache keyed by backtest_id, and a cache hit returns before any query runs — so a second user asking for a cached id would have been handed the first user's result without SQLite being consulted. The key is (user_id, backtest_id).

Another user's backtest is 404, not 403. A 403 confirms the id exists, which turns every result endpoint into an oracle for enumerating other people's runs.

Abuse controls — CAPTCHA, rate limits, verified addresses
Control Where
Cloudflare Turnstile Registration, forgot-password, forgot-username
Per-IP signup budget Registration and both recovery endpoints
Per-(ip, username) throttle Password login
argon2id Every stored password
SHA-256 hashed tokens Sessions, verification links, reset links

Turnstile fails closed. Once a key is configured, a token that cannot be checked — Cloudflare unreachable, timeout, malformed reply — is a refusal. A verifier that waves people through on a network hiccup is one outage away from being no verifier, and an attacker can cause that outage.

Only a provider-verified address matches an account. An unverified address is a claim by whoever is signing in; honouring it would let someone assert another person's email and be handed the account that owns it.

Dormant until configured — nothing breaks without credentials

Every integration treats "no key" as a supported state rather than an error.

Environment variable Unset behaviour
AUTOTRADER_{GOOGLE,LINKEDIN,GITHUB,TWITTER}_CLIENT_ID / _SECRET That provider reports itself unconfigured and its button says so
AUTOTRADER_TURNSTILE_SITE_KEY / _SECRET_KEY The CAPTCHA slot collapses; registration still works
AUTOTRADER_RESEND_API_KEY, AUTOTRADER_MAIL_FROM Verification and reset emails are logged as dormant, never faked

The deploy prints present: / ABSENT : per integration — by presence only, never a value or a length.



🔌 API Reference

FastAPI, with interactive documentation at /docs while running.

Every endpoint — area, method, path and what it is for
Area Method Endpoint Purpose
Replay POST /api/replay Create a session
Replay WS /api/replay/ws/{id} Drive it bar by bar
Replay WS ↳ action: extend Pull bars that printed since
Backtest POST /api/backtests Run and score a strategy
Market GET /api/symbols Instruments and their specs
Market GET /api/strategies Available strategies and parameters
Schwab GET /api/schwab/status Auth state and token life
Export GET /api/export/... CSV · XLSX · PDF · DOCX
Auth POST /api/auth/register Create an account
Auth POST /api/auth/login · /logout Start and end a session
Auth GET /api/auth/me Who the caller is
Auth POST /api/auth/forgot-password · /reset-password Recover by email
Auth POST /api/auth/forgot-username Email the account name
Auth GET /api/auth/verify-email Spend a confirmation link
OAuth GET /api/auth/oauth/providers Which providers are configured
OAuth GET /api/auth/oauth/{name}/start · /callback Google · LinkedIn · GitHub · X
OAuth POST /api/auth/oauth/complete Finish an X sign-up
Meta GET /api/version Build commit — used by the deploy assertion

See routers and the API Guide.



🛠 Technology Stack

Backend, frontend and infrastructure — every dependency that shows on screen

⚙️ Backend

Python 3.12 FastAPI Uvicorn pandas NumPy pandas-ta Pydantic pytest Ruff mypy bandit

🖥 Frontend

React 19 TypeScript Vite Tailwind_CSS shadcn/ui Radix_UI Recharts Vitest oxlint

☁️ Infrastructure

Docker Docker_Compose GitHub_Actions AWS_EC2 GitHub_App_auth Nginx


📂 Folder Structure

Tip

Every name below is a link. Click a folder to open it, or a file to read it. Each directory also carries its own README explaining what it holds and why.

Full directory tree — every file and folder, each name a link
trading-platform
|------------▶  src   the engine — no HTTP, no React, no framework  61
|               |------------▶  analysis   reading the market  22
|               |               |- - - ▶  candlestick_patterns.py
|               |               |- - - ▶  chart_patterns.py
|               |               |- - - ▶  indicators.py
|               |               |- - - ▶  regime.py
|               |               |- - - ▶  swing_identification.py
|               |               └- - - ▶  zigzag.py
|
|               |               └------------▶  elliott_wave   wave detection, rules and hierarchy  14
|               |                               |- - - ▶  combination.py
|               |                               |- - - ▶  correction.py
|               |                               |- - - ▶  diagonal.py
|               |                               |- - - ▶  hierarchy.py
|               |                               |- - - ▶  impulse.py
|               |                               |- - - ▶  measurements.py
|               |                               |- - - ▶  models.py
|               |                               |- - - ▶  momentum.py
|               |                               |- - - ▶  pipeline.py
|               |                               |- - - ▶  pivots.py
|               |                               |- - - ▶  triangle.py
|               |                               └- - - ▶  validation.py
|
|               |------------▶  strategies   turning a reading into a decision  8
|               |               |- - - ▶  base_strategy.py
|               |               |- - - ▶  breakout.py
|               |               |- - - ▶  ma_crossover.py
|               |               |- - - ▶  regime_adaptive.py
|               |               |- - - ▶  rsi_divergence.py
|               |               └- - - ▶  rsi_mean_reversion.py
|
|               |------------▶  backtesting   replay engine and the shared market clock  8
|               |               |- - - ▶  engine.py
|               |               |- - - ▶  metrics.py
|               |               |- - - ▶  multi_replay.py
|               |               |- - - ▶  replay_engine.py
|               |               |- - - ▶  results.py
|               |               └- - - ▶  trade_quality.py
|
|               |------------▶  broker   what a fill actually costs  5
|               |               |- - - ▶  base_broker.py
|               |               |- - - ▶  paper_broker.py
|               |               └- - - ▶  rithmic_broker.py
|
|               |------------▶  data   providers, and one shared resampler  12
|               |               |- - - ▶  base_provider.py
|               |               |- - - ▶  csv_provider.py
|               |               |- - - ▶  external_csv_provider.py
|               |               |- - - ▶  resample.py
|               |               |- - - ▶  rithmic_provider.py
|               |               |- - - ▶  sample_data.py
|               |               └- - - ▶  schwab_provider.py
|
|               └------------▶  live   live loop (experimental)  3
|                               └- - - ▶  trader.py
|------------▶  api   the FastAPI service  32
|               |------------▶  routers   REST endpoints and the replay socket  8
|               |               |- - - ▶  backtests.py
|               |               |- - - ▶  data_export.py
|               |               |- - - ▶  meta.py
|               |               |- - - ▶  optimize.py
|               |               |- - - ▶  replay.py
|               |               └- - - ▶  schwab.py
|
|               |------------▶  schemas   request and response models  7
|               |               |- - - ▶  backtest.py
|               |               |- - - ▶  elliott_wave.py
|               |               |- - - ▶  optimize.py
|               |               |- - - ▶  replay.py
|               |               └- - - ▶  schwab.py
|
|               |------------▶  report   charts rendered on the server  4
|               |               |- - - ▶  charts.py
|               |               └- - - ▶  report.py
|
|               └------------▶  export   CSV · XLSX · PDF · DOCX  4
|                               |- - - ▶  formats.py
|                               └- - - ▶  report_export.py
|------------▶  web   the React dashboard  92
|               |------------▶  features   replay, backtest and export pages  5
|
|               |------------▶  components   shared UI  34
|
|               └------------▶  lib   pure logic, unit-tested away from React  49
|                               |- - - ▶  api.ts
|                               |- - - ▶  bandAgreement.test.ts
|                               |- - - ▶  bandAgreement.ts
|                               |- - - ▶  chartAxis.test.ts
|                               |- - - ▶  clock.test.ts
|                               |- - - ▶  clock.ts
|                               |- - - ▶  dayRange.test.ts
|                               |- - - ▶  dayRange.ts
|                               └- - - ▶  +41 more
|------------▶  tests   what every number on screen rests on  37
|               |- - - ▶  test_api_provider_errors.py
|               |- - - ▶  test_engine.py
|               |- - - ▶  test_follow_live_matrix.py
|               |- - - ▶  test_indicator_correctness.py
|               |- - - ▶  test_multi_replay.py
|               |- - - ▶  test_provider_timeframes.py
|               |- - - ▶  test_daily_weekly.py
|               └- - - ▶  +30 more
|------------▶  docs   architecture, rules and guides  22
|               |- - - ▶  API_GUIDE.md
|               |- - - ▶  Design Document.md
|               |- - - ▶  CONFIGURATION.md
|               |- - - ▶  DEVELOPER_GUIDE.md
|               |- - - ▶  ELLIOTT_WAVE.md
|               |- - - ▶  ELLIOTT_WAVE.md
|               └- - - ▶  +10 more
|------------▶  config   settings and credential templates  3
|               |- - - ▶  credentials.yaml.example
|               └- - - ▶  settings.yaml
|------------▶  db   SQLite schema, connection and the result repository  5
|               |- - - ▶  schema.sql
|               |- - - ▶  connection.py
|               └- - - ▶  backtests.py
|------------▶  data   bundled samples, downloads, and saved results  18
|------------▶  scripts   CLI entry points and the local launcher  5
|               |- - - ▶  download_rithmic_data.py
|               |- - - ▶  generate_data.py
|               |- - - ▶  run-autotrader.cmd
|               └- - - ▶  run_backtest.py
|------------▶  reports   generated output  3
                └------------▶  workflows   CI and deploy  3
                                |- - - ▶  ci.yml
                                |- - - ▶  deploy.yml
                                └- - - ▶  oauth-logs.yml


🚀 Getting Started

◆ Prerequisites

Requirement Notes
🐍 Python 3.12 Not 3.14 — pandas_ta breaks on it and produces a test failure that looks real but is not
🟢 Node.js 20+ For the dashboard
🔑 Schwab credentials Only for live data. CSV and synthetic sources work without any

◆ 1 · Clone and install

git clone https://github.com/wealthwise-advisors/trading-platform.git
cd trading-platform

py -3.12 -m pip install -r requirements.txt
cd web && npm install && cd ..

◆ 2 · Configure

cp config/credentials.yaml.example config/credentials.yaml

Fill in your Schwab app key and secret.

Caution

credentials.yaml and schwab_tokens.json are gitignored and must never be committed. Five predecessor repositories hardcoded live credentials into source files, and every one had to be rotated. That is the cost this rule exists to avoid.

◆ 3 · Run it

# API  →  http://127.0.0.1:8000
py -3.12 -m uvicorn api.main:app --reload

# Web  →  http://localhost:5173
cd web && npm run dev

On Windows, run-autotrader.cmd does both — frees the ports first and pins the right Python.

Tip

Open http://localhost:5173, not 127.0.0.1:5173. Vite binds to IPv6 only, so the numeric address is refused — that "site can't be reached" error is the address, not a broken app.

◆ 4 · First run

1Data Source → Live Data (Schwab)
2Start and End → today's date, both
3Tick 1m under Timeframes
4⬇ Load Data → ▶ Play
5Touch nothing else. At 100% the tape starts following the live market on its own


🧪 Testing & Quality

tests python web coverage gate


◆ Commands

Four checks. Each answers a different question, and none substitutes for another.

▶ Command ➜ Answers
py -3.12 -m pytest Does the engine still compute what it computed before?
cd web && npm test Does the pure frontend logic still hold?
cd web && npm run test:e2e Does it still work in a browser?
cd web && npm run build Does it actually typecheck? This one, not tsc --noEmit
py -3.12 -m ruff check . Is the style and the import graph clean?
py -3.12 -m pytest --cov=src --cov=api --cov-report=term How much of it is exercised?

Warning

npx tsc --noEmit reports success on broken JSX. The root tsconfig.json is a solution file carrying project references only, so there is nothing for it to check. npm run build (tsc -b) is the real typecheck — it is the only command that will fail on a type error.


◆ End-to-end, in a real browser

npm test runs in jsdom, which has no layout engine. It cannot tell you that an element sits 260px below the fold, that a scroll container does not scroll, or that one stylesheet's rules beat another's — and each of those has reached production here while every other check stayed green. That is what this suite is for.

cd web
npx playwright install chromium   # once, ~110 MB
npm run test:e2e                  # ~90s, Chromium only
npm run test:e2e:ui               # pick and step through individual tests
npm run test:e2e:report           # open the report from the last failing run

Nothing needs to be running first. Playwright builds the app, starts a throwaway API on its own SQLite file, serves the build, registers a test account and tears it all down again. Your own data/autotrader.db and anything on ports 5173/8000 are never touched.

▶ ➜
Data The app's own synthetic source. No Schwab token, no credential of any kind — and none should ever be added.
Ports 4188 (built app) and 8188 (API), chosen clear of the dev ports.
Retries One. A trace and a screenshot are kept only for a failure.
Waits None. Every step waits on a condition, never a timer.

Note

The e2e job runs on pull requests and on pushes to master. It is not in the deploy path: deploy.yml does not wait on it, so a flaky browser test can never stand between a production fix and production.


◆ What is covered

Suite Count ➜ Covers
🐍 Python 1,730 Engine · analysis · API · providers · replay · accounts
⚛️ Web 492 Pure logic in lib
📦 Total 2,026
📊 Coverage 83.2% src and api, measured on every push
  • ➜ Gated at 70%, deliberately below the current 83.2% — a threshold pinned to today's number gets lowered the first time it fails
  • ➜ Excludes the vendored Schwab client, as ruff and mypy already do — it is third-party code nobody here will change
  • ➜ Runs in the same CI step as the tests, so coverage cannot silently stop being measured

◆ Three kinds of test, three meanings of red

The suite splits into three kinds: unit tests where a failure means a mechanism broke, behavioural matrices where a failure means a rule about what you are shown broke, and confirmed baselines where a failure means something already verified has changed
Kind ➜ A failure means ➜ So you should
🔧 Unit A mechanism broke Fix the mechanism
🧭 Behavioural matrix A rule about what you are shown broke Decide which rule is right
📌 Confirmed baseline Something already verified has changed Ask whether you meant it

Important

Baseline values were confirmed against real backtests and a reference trading platform. A failure asks "did I mean to change this?" — never "update the numbers to match." Rewriting a baseline to match new output deletes the only evidence that the old output was ever right.


◆ What the tests defend

Area ➜ The guarantee
📡 Follow-live All thirteen timeframes · every position within a bar · seven combinations · five dates, including both DST switches and a leap day
⏱ Bar aggregation One aggregator — so no two code paths can disagree about what a bar is. Minutes tile from the exchange midnight; 1d and 1w group by trading date, where an 18:00 ET open belongs to the next day
🔁 Determinism A session grown bar by bar is byte-identical to one handed all the data at once
🌊 Elliott Wave A count that breaks a rule is rejected, not drawn
🚧 Isolation One account cannot reach another's data — routes swept from the app's own OpenAPI schema
⚠️ Error messages A bad request names the field to change. It never returns a 500

◆ How a test earns its place

A test that cannot fail defends nothing. Each one here was written against a bug that had already shipped, and checked against the commit that shipped it.

Evidence Detail
🐛 The bug Follow-live worked at 1m and broke above it
🧪 The test test_follow_live_matrix.py — 107 cases
🔴 Proof it bites 67 of 107 fail against the commit that shipped the bug
✅ Proof it passes All 107 pass against the fix

Tip

Before trusting a new test, revert the fix and watch it fail. A test written after the fix, never run against the bug, is an assumption with a green tick beside it.



📦 Deployment

GitHub GitHub Actions Docker Compose AWS EC2 Nginx


◆ The pipeline

Pull request, CI, merge to master, deploy workflow and AWS EC2; the run then checks whether the served commit equals github.sha, confirming the deployment only if it does and failing otherwise
   push to master
        │
        ▼
   ┌──────────┐   green   ┌──────────┐   ┌──────────────┐   ┌─────────────┐
   │    CI    │ ────────► │  build   │ ─►│  ship to EC2 │ ─►│   VERIFY    │
   │  tests   │           │  images  │   │  compose up  │   │  which sha? │
   └──────────┘           └──────────┘   └──────────────┘   └──────┬──────┘
                                                                   │
                                    ┌──────────────────────────────┴───────┐
                                    ▼                                      ▼
                        sha == github.sha                        sha != github.sha
                        ✅ CONFIRMED                              ❌ RUN FAILS

◆ What each stage guarantees

Stage ➜ Guarantee
🧪 CI Nothing reaches the server unless every test passed on that exact commit
🐳 Build Images are built from the checked-out tree, not from a cache of a previous one
🚢 Ship docker compose up with pinned volumes, so data survives the replacement
🔍 Verify The running server is asked which commit it serves

◆ Safety properties

Item ➜ Detail
🔐 Auth GitHub App installation token, minted per run and revoked when the job ends — no long-lived key sits in the repository
🛡️ Firewall The SSH rule is opened for the runner's IP alone and always revoked, in an always() step, so it closes even when the run fails
🔑 Tokens Schwab tokens follow newer-wins, so a container's own refresh is never clobbered by a redeploy
📁 Volumes Pinned by path, so the database is not recreated underneath the new containers
📄 Read bits Repaired after checkout — a leaked umask once made every new file mode 600, and nginx could not read them

◆ The proof

Important

Every deploy asks the running server which commit it is serving, and fails the run unless it matches github.sha.

CONFIRMED: port 80 is served by 104bb20...
  • ➜ It catches the silent failure — a container that failed to restart, or a cached image, leaves the old build serving
  • ➜ Without it, a deploy is green when nothing deployed — the most expensive kind of green there is


🗂 Repository Ecosystem

Five retired repositories converge through a redaction step into trading-platform, which reads full market history from the data repository

◆ The two live repositories

Repository ➜ Holds ➜ Why separate
🏗 trading-platform The application — engine, API, frontend, tests you are here —
📊 data Full market history in Git LFS, including an 18-year 1-minute ES series A single 335 MB file exceeds GitHub's 100 MB limit, so it cannot live in an ordinary repository Open ↗
  • ➜ The code repository stays small. Cloning this one does not pull 433 MB of bars
  • ➜ Docker images stay lean. None of the market data enters a build
  • ➜ sample is enough to run everything — 5,000-row slices ship with the code, so the tests and a first run need no download

◆ What was retired

Five predecessor repositories — trading-strategy, trading-web, Wealthwise, backtest and Project_work.

Fact ➜ Detail
🔄 Relationship This platform is a rebuild, not a merge. None of their code runs here
🗑 Their GitHub originals Deleted. All four were removed from the organisation
📦 Their 519 files Archived here, then removed from the working tree to keep it clean
💾 Recoverable Yes, permanently — they remain in git history, pinned by a tag
🔒 Credentials Hardcoded secrets were stripped on the way in, and every one was rotated

Note

Nothing was lost when legacy/ was removed. Deleting a folder from the working tree does not delete it from git history, and a tag pins the commit that still holds all 519 files:

git show archive/legacy-2026-08-28:legacy/README.md      # read it, without restoring
git checkout archive/legacy-2026-08-28 -- legacy/        # restore the whole folder


📚 Documentation

◆ Getting oriented

◆ What it is meant to do

◆ Going deeper

Every directory also has its own README — src · api · lib · tests · config and the rest.



⚠️ Disclaimer

Caution

This is analysis software. It is not investment advice, it does not manage money, and it does not place orders on your behalf.

  • ➜ A backtest is a record, not a prediction — it shows what a rule would have done on bars that already printed, over a period chosen in hindsight, knowing how it ended
  • ➜ Past performance does not indicate future results — true of every backtest ever run, including every one in this repository
  • ➜ Futures trading carries substantial risk of loss and is not suitable for every investor
  • ➜ You can lose more than you deposit — leverage works in both directions
  • ➜ Costs are modelled, not complete — commission, slippage and tick rounding are charged; partial fills, gaps, outages and rejected orders are not
  • ➜ Market data can be delayed, incomplete or wrong — it arrives from third parties
  • ➜ Live order execution is not wired — the live trading path is a deliberate stub and the Deploy button is disabled to match. A half-built order path that looked finished would be more dangerous than an honest gap
  • ➜ Nothing here should be traded with real money without independent validation and your own understanding of the risk


📄 License

Proprietary

© WealthWise Advisors. All rights reserved.

View License


🏛 Ownership

 Owner  Organization  Contact Info
Sriram Nandepu WealthWise Advisors GitHub GitHub Repo — snandepu
GitHub Organization GitHub Repo — wealthwise-advisors

💻 Developed By

 Developer  Contact Info
Akash Yadav Gmail Gmail — akashyadav110502@gmail.com
Phone Phone — +91 70053 63923
LinkedIn LinkedIn — akash-yadav-122a75288
GitHub GitHub Repo — akxyverse

🌐 Project Info

Project Repo

github.com/wealthwise-advisors/trading-platform

Project Link

3-218-23-37.sslip.io


About

Algorithmic trading research and execution platform for futures — test a strategy against real market data bar by bar, replay the tape as it happened, and follow it live.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages