Solo operator (design, build, run)

FastAPI decision-support system for my own trading.

Daily trade decisions I trust enough to act on

I build single-operator systems where every claim — including the demo's — has to trace back to something that actually runs.

Open live app
At a glance

System.

Interactive snapshot of how this project actually works.

SYNTHETIC · last 20 trading days
Kronos forecast horizon overlay · rule fires mark accuracy + threshold hits
R1 BUY R2 SELL R3 BUY+
Day 1 → 20 · synthetic data, illustrative
R1 BUY +2% over 5d
≥ 60% hit-rate · ≥ 10 samples
Fires when predicted move ≥ +2% AND horizon = 5 days AND historical hit-rate clears the gate.
R2 SELL −2% over 5d
≥ 60% hit-rate · ≥ 10 samples
Mirror rule for downside moves. Same threshold gates, opposite direction.
R3 BUY +5% over 10d
≥ 55% hit-rate · ≥ 10 samples
Slightly relaxed hit-rate floor because larger moves are inherently noisier.
Prediction
Rule eval
Opportunity (open)
Acted · trade journal
Per-rule P&L
Read

Depth selector.

Brief · what it is. Approach · how it works. Deep · why the calls.

A daily decision-support pipeline for my own trading. Pulls a watchlist of symbols, runs Kronos candlestick forecasts, scores them against historical hit-rates, and surfaces opportunities that clear specific thresholds. Trades I actually take get journaled by hand so per-rule P&L attribution becomes a real number, not an estimate.

Tier 2

I wanted to know whether the model was actually right before I trusted any signal it produced — so I sequenced the build around that question.

  • Phase 1 — accuracy and drift on bare Kronos forecasts
  • Phase 3 — once that produced real hit-rates, turn predictions into BUY/SELL opportunities
  • Phase 5 — hang the trade journal off opportunities so per-rule P&L attribution can exist at all

The non-obvious call: the public demo is a separate, secret-less branch with its own deploy.

Every number, rule name, or capability claim on the demo must trace back to something that actually runs in main. No invented precision. No aspirational features.

That constraint shaped what I let myself say here, too.

Tier 3

System

A FastAPI monolith with one operator, one API key, and Postgres as the only durable store.

Kronos candlestick forecasts run on a daily-scheduled watchlist. app/accuracy/ evaluates them as actuals land and feeds hit-rates back into app/opportunities/, where three hardcoded rules in app/opportunities/rules.py emit signals into a lifecycle table:

RuleTriggerGate
R1BUY +2% over 5d≥ 60% hit-rate · ≥ 10 samples
R2SELL −2% over 5d≥ 60% hit-rate · ≥ 10 samples
R3BUY +5% over 10d≥ 55% hit-rate · ≥ 10 samples

Opportunities get acted on, dismissed, or expired. Acting on one routes the operator into the manual trade journal with a prefilled link back to the originating opportunity.

That link is the spine of /v1/trades/pnl/by-rule — the question “if I’d taken every R1 signal, what would I actually have made?” only has an answer because the trade journal is hand-logged and tagged.

Brokerage integration was explicitly rejected. The friction of OAuth and position reconciliation for one user wasn’t worth the loss of deliberate tagging.

Two recent calls

ADR 018 — Railway shutdown (2026-05-17). Collapsed the dual-backend topology to laptop-only.

The replica had been useful for read-access when the laptop was asleep, but the modules that actually create value — vault indexer, research stress-tests, TV-context ingest, Kronos inference — were always laptop-only anyway. Sync-outbox maintenance + Railway cost weren’t paying for themselves. BackendToggle and “Admin UI is laptop-local” banners came out the same day. The sync code was left dormant rather than ripped — the rows in the DB would have required a migration rollback to remove cleanly.

Demo-branch verifiability discipline. The demo lives on demo, builds on Cloudflare Pages, hits a stripped Railway FastAPI image with no secrets, no database URL, and no API key.

  • GET /health{"status":"ok","mode":"demo"}
  • POST /v1/research/ask404 by design

The discipline isn’t the deploy. It’s that I audit every claim on that public surface against the real rule labels in app/opportunities/rules.py and the real snapshot query in scripts/bake_demo_snapshot.py, and soften to qualitative language anywhere I can’t ground a number.

The cheapest way I’ve found to keep a marketing surface honest.

What doesn’t work yet

  • Per-rule P&L is mathematically correct but only meaningful once enough trades link back to opportunities to be more than anecdote. Calendar time, not engineering time.
  • Opportunity rules are hardcoded — no DSL. Deliberate until I’ve tuned thresholds by hand long enough to know what the DSL even needs to express.
  • Single-operator assumption is load-bearing through every layer (no tenancy, no roles, no audit log). A second user wouldn’t be a feature flag — it would be a rewrite.

Where this fits

The finance recommendation surface that sits on top of this — app/rx/ writing to a local Postgres recommendations table, dispositioned through a TradingV panel — is one of three “doors” for an operator-facing recommendation pattern I run across domains.

The pattern itself — storage routing, attention scoring, snooze caps, decisions ledger — is documented on /operating-model.

This page is the finance instantiation of it.