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.
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.
| 2,709 | 83.2% | 3.12 | 5 |
| tests passing | coverage | Python | strategies |
Verified by the runners, not typed from memory — see Testing & Quality.
|
◆ Understanding it |
◆ How it works |
◆ Working with it |
➜ 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.
|
◆ If you trade and want to check an idea;
|
◆ If you build software and want to read the engine;
|
Note
Three ways in, depending on how much time you have;
Five minutes: Quick Look and Why It Exists, the four bugs that shaped it.
An hour: Quickstart and a real backtest.
A day:
docscarries the product requirements, the requirements spec and the Elliott Wave rule inventory.
➜ 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:
|
|
➜ 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. |
|
|
➜ 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 |
|
|
➜ 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. |
|
|
➜ A deploy that quietly changes nothing. ➜ The pipeline asks the running server which commit it is serving and fails the run unless it matches. |
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.
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.
|
|
|
|
|
|
- ➜ 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.
- ➜ 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
|
Data flow
|
Design principles
|
How a change actually travels from an idea to the live URL.
| Stage | What has to be true to move on |
|---|---|
| Research | The 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 |
| Implement | A test exists that fails against the previous commit. A test that passes either way defends nothing |
| Verify | 2,227 Python tests, 492 web tests, ruff, and npm run build — not tsc --noEmit, which reports success on broken JSX |
| Deploy | The running server is asked which commit it serves. Mismatch fails the run |
| Signal | What it actually measures |
|---|---|
| CI | The workflow's own conclusion on master — lint, type check, unit tests, security audit, frontend tests and a package build |
| Deploy | Live 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 |
| Tests | 2,709 passing — 2,217 of 2,227 Python (10 skipped) and 492 web. Counted from pytest and vitest, not estimated |
| Coverage | Line coverage of src and api, from the TOTAL row of pytest --cov=src --cov=api. The CI gate is 70% |
| License | Proprietary — see LICENSE |
|
|
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.
- ➜ 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 |
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
- ➜ 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
1dor1wbar 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
➜ 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
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 ➜ 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.
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
Open registration. Four ways in, or a password.
|
◆ Ways to sign in
|
◆ Getting back in
|
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.
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.
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
| 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 |
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 ..cp config/credentials.yaml.example config/credentials.yamlFill 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.
# API → http://127.0.0.1:8000
py -3.12 -m uvicorn api.main:app --reload
# Web → http://localhost:5173
cd web && npm run devOn 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.
| 1 | Data Source → Live Data (Schwab) |
| 2 | Start and End → today's date, both |
| 3 | Tick 1m under Timeframes |
| 4 | ⬇ Load Data → ▶ Play |
| 5 | Touch nothing else. At 100% the tape starts following the live market on its own |
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.
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 runNothing 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.
| 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
ruffandmypyalready 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
| 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.
| 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 |
| A bad request names the field to change. It never returns a 500 |
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.
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
| 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 |
| 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 |
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 | ➜ 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
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
|
|
Every directory also has its own README — src · api · lib · tests · config and the rest.
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
Proprietary
© WealthWise Advisors. All rights reserved.
| Sriram Nandepu | WealthWise Advisors |
| Akash Yadav |
Project Repo
Project Link
