Back
Personal Project

Sniper

A US-equity catalyst engine — it hunts stocks about to be re-priced by a known catalyst, scores them against a 22-setup library, and emits structured entry tickets. yfinance-powered, full-stack.

Bun · ElysiaPython · yfinancePostgresReact 19
Sniper cover

Private project — no live demo

Sniper is my own trading-research engine. It runs locally against live broker and market-data APIs with private credentials, so there's no public demo — the diagrams and walkthrough below cover how it works end to end.

Overview

Sniper is a US-equity catalyst recommendation engine — the most ambitious thing I've built. It hunts for stocks that are about to be re-priced by a known catalyst, scores each one against a fixed library of 22 catalyst playbooks, and emits a structured entry ticket: a verdict, a 0–100 composite score, position sizing, and an entry/stop/target plan.

It is deliberately a recommendation engine, not an autotrader. Sniper surfaces and scores; the human decides and executes. Every run ends at a ticket, and what I actually did with it gets logged back so the engine's calibration can improve over time.

It's powered by Yahoo market data through yfinance, and it's a full-stack, polyglot system: a Python data sidecar, a TypeScript scoring backend, and a React dashboard.

The Core Idea

The whole system is built around one sentence:

Big move = surprise + believable upside + people under-positioned + catalyst timing.

I'm not looking for "good stocks." I'm looking for a reason the market is currently wrong and a catalyst that will soon force it to realise that. A catalyst on its own isn't enough — the market has to be mispriced relative to what that catalyst is about to deliver. Every setup in the system is evaluated through that single lens, which is why two of the scoring pillars (Catalyst Strength and Expectations Gap) carry the heaviest weight.

Architecture

Sniper is three tiers connected by exactly one HTTP hop from Yahoo to the browser.

The three tiers — a Python sidecar for raw data, an Elysia brain, and a React dashboard
The three tiers — a Python sidecar for raw data, an Elysia brain, and a React dashboard

  • A Python sidecar (FastAPI) talks to Yahoo and returns raw data only.
  • An Elysia backend (Bun + TypeScript) is the brain: it normalizes, persists, scores, and serves a REST API.
  • A React frontend (React 19 + Vite) is a thin, read-mostly UI over those endpoints.

The boundary between data and logic is strict and intentional, which is what keeps each tier simple.

The Python Sidecar

The sidecar has exactly one job: call yfinance and hand back Yahoo-native JSON — calendars (earnings, splits, IPOs, economic), per-ticker info, estimates and news, and batched daily adjusted closes. It does the only transform that has to happen in Python — DataFrame to records, NaN to null, timestamps to ISO — and nothing else.

Everything that smells like schema, naming, persistence, or scoring lives on the TypeScript side. That separation means the data layer never has an opinion about the domain, and adding a new data source is just one new Python file with a router.

The Engine

The analytical core is three orthogonal layers, and keeping them orthogonal is what makes the system extensible.

Workflows surface candidates, setups score them, enrichments add context
Workflows surface candidates, setups score them, enrichments add context

  • Workflows (W01–W12) are pluggable hunting patterns — pre-earnings positioning, post-earnings drift, the mega-deal 8-K stream, the biotech binary calendar, insider-cluster scans, activist 13D watches, and more. Each scans its own feeds and surfaces candidates.
  • Setups (22) are the catalyst playbooks — M&A, FDA binaries, short squeezes, margin inflection, index inclusion, buybacks, spin-offs, insider clusters, and the rest. A setup defines what a candidate is scored against.
  • Enrichments (E1–E4) are cross-cutting context — squeeze fuel, options positioning, sympathy baskets, and sector regime — attached to every candidate before it's scored.

The key design rule: workflows never score, and setups never fetch their own data. A workflow's job ends at producing pre-screened candidates; a setup scorer receives a candidate plus its enrichment bundle and returns pillar scores. The "evaluate" half is shared by everyone.

The Intake Pipeline

Because scoring is shared, every candidate — no matter which workflow surfaced it — flows through the exact same pipeline.

Disqualify, enrich, score, calibrate — one shared, idempotent path to a ticket
Disqualify, enrich, score, calibrate — one shared, idempotent path to a ticket

Disqualify against universal red flags, enrich with cross-cutting context, score with the five pillars, then calibrate into a sized, plan-bearing ticket. The pipeline is idempotent: the dedup key is (ticker, setup_id, signal_key), so a fresh signal updates an existing candidate rather than spawning a duplicate.

The Scoring Framework

Discretionary "I like this trade" is the cheapest way to lose money, so synthesis is handed to a fixed composite rather than a gut feeling. The LLM's job is feature extraction — parse the 8-K, score the narrative, find analogues — and the composite's job is synthesis. Neither impersonates the other.

A finished ticket — five weighted pillars, a composite, a verdict, and a sized trade plan
A finished ticket — five weighted pillars, a composite, a verdict, and a sized trade plan

Every candidate is scored 0–100 across five weighted pillars:

PillarWeightMeasures
Catalyst Strength25the catalyst type's historical edge × how specific this instance is
Setup Quality20technical and fundamental confirmation
Expectations Gap25is the market actually under-positioned?
Risk Control15asymmetric reward/risk with a defined invalidation
Time-to-Catalyst15proximity premium

The composite resolves to a verdict — CANDIDATE, WATCHLIST, or SKIP — and a trade plan sized in percent of risk capital against a $100 working base (so "2.5%" reads as plain math), with Kelly-capped sizing and Brier-tracked calibration feeding back from the postmortem log. Setup 01's scorer is fully deterministic and covered by golden regression tests against real names.

The Catalyst Calendar

The primary surface is the events calendar shown at the top of this page. A week strip summarises each day's earnings, economic events, IPOs, and splits at a glance; below it, sortable tables drill into each. The earnings table carries a 10-day evaluation window — close-to-close returns ordered 7d → 1d → +1d → +3d so a name reads as a time-series leading into its print — and each row ends in a verdict badge and composite score that open the full text ticket in a dialog.

All of the heavy lifting runs as background jobs kicked off from here: refresh the calendar, enrich tickers, compute the price evals, run the scorecards.

Brokers & Money

Sniper connects to real brokers for context. Indmoney is read-only — holdings, positions, funds, and the order book. Alpaca is a multi-account integration that can place and close real orders (the one place the system touches execution). A separate finance ledger tracks named accounts as an append-only log, deriving invested capital and profit from the entries rather than storing balances — capital and profit kept on separate rails.

Live Jobs & Monitoring

Long-running work — fetching tickers, refreshing calendars, computing evals, scoring — runs as backend jobs with progress streamed over a WebSocket. A persistent monitor in the navbar shows what's in flight and, when a job finishes, invalidates exactly the right query caches so the UI refreshes itself without a manual reload.

Recommendation, Not Execution

This boundary is a feature, not a limitation. Sniper surfaces, enriches, scores, and explains — then stops. The human reviews the ticket and decides; afterwards, the postmortem log records the recommendation, the action taken, and the realised outcome, which is what the calibration layer learns from. The engine is built to be a sharp analyst, not an impulsive one.

In Development

Several surfaces are scaffolded and on the roadmap, reachable from the navbar but not yet built out:

  • Workflows — a live view of W01–W12 runs and the candidates each surfaced.
  • Revenue Report — a consolidated revenue/estimates view per name.
  • News — the news firehose filtered to tracked tickers.
  • Gold Cart — a curated shortlist of the highest-conviction tickets.

The Stack

Backend: Elysia on Bun with TypeScript, Postgres via Drizzle, a single LLM service over the Vercel AI SDK with file-based system prompts. Data: a Python FastAPI sidecar wrapping yfinance. Frontend: React 19 + Vite, Tailwind v4, @base-ui/react primitives, React Router, and TanStack Query. The throughline is the same everywhere — keep the data layer dumb, keep the scoring deterministic, and let the structure of the system document what it does.

More projects