Sophia
Design onlyAttacks algorithmic trading from the side the HFT desks can't defend, using longer horizons and much broader signals, with one ontology built to keep them honest.
At a glance
- Outcome: A design that picks a fight it can win. Instead of competing on latency with colocated trading desks, it goes the other way: longer horizons, broader signals, and a service that owns that context so its consumers don’t have to.
- Status: Design only, and that was the point. Four days in April 2026 spent settling the ontology before any ingestion code could harden around a wrong one. The repository is 29 markdown files and no code, and nothing has ingested a real row.
- Role: Solo. The ontology, the data model, the action contract.
- Stack (planned): Python only, with the TypeScript client generated from the API schema instead of hand-maintained. Postgres with time-series extensions, row-level security for tenancy, and a bot-facing HTTP API.
- Source: Private.
- Limitations: Calibration against real outcomes is the part that matters and the part that isn’t done. But the more honest limitation is upstream of that: nothing has been built. This is a specification detailed enough to be wrong in interesting ways, and it hasn’t been given the chance.
Choosing a game you can actually win
Most retail algorithmic trading competes on the axis the big firms are structurally best at, which is speed. Those firms have machines in the same building as the exchange, custom silicon, and venture funding. On latency you lose before you start, not because your code is worse but because your distance to the matching engine is measured in miles and theirs in meters.
So Sophia takes the opposite bet. Move the horizon out far enough that latency stops mattering, and the binding constraint becomes something you can actually be good at: how much context you can gather, reconcile and keep honest. Over hours and days instead of microseconds, the edge is breadth and correctness of signal, which is a data-modeling problem instead of a physics problem.
That reframing generates the rest of the design. A long horizon needs many weak signals instead of one fast one, which is why the scope is ten named source families over equities and ETFs: earnings, filings, insider activity, corporate actions, mergers, regulatory, macro, ETF context, press releases and options flow. Weak and numerous signals have to combine in a way you can inspect, which is why there’s an ontology at all. And if you ever want to train on the result, tomorrow’s knowledge can never leak into yesterday’s snapshot, which is where the point-in-time work comes from.
Broad news and social sentiment are a written non-goal, which is the same discipline in miniature. Breadth is the bet, but not breadth at any price.
The consumer problem it solves
A trading bot that gathers its own context ends up owning two jobs badly. Market data and corporate events arrive in different shapes on different schedules from sources that disagree, and the normalization logic quietly becomes the largest and least-tested part of the bot.
Sophia takes that job away. Ingestion, resolution and normalization happen once, behind a versioned ontology, and a consumer asks a clean API for a symbol’s current view.
The review that reversed itself
The first version of this design was model-first. Train a model per source family, treat whatever numbers it emits as the contract, run a separate API next to the workers, and publish add, hold, trim, exit.
The review reversed all three.
Ontology first, because a contract that says “these numbers are whatever the model emits” leaves nothing to debug against when the model overfits. A rule-based generator and a learned one have to be interchangeable implementations of the same stated meaning. One language, because those are package boundaries, not a reason to run two toolchains. And two position-independent signals, because the classic action labels need position state Sophia doesn’t have.
The original brainstorm is still in the repo with a note at the top listing what it got wrong.
A subsignal is not a score
This is the noun the design invented, and it’s what the ontology buys.
A signal record carries its value in its own native units, along with how confident it is, how much coverage it has, which direction it points, how fast it decays, when its evidence was actually known, and pointers back to the rows it came from. Earnings surprise stays in units of consensus dispersion. Insider buying stays a fraction. A rate stays in basis points.
Normalization happens once, later, when a snapshot is built, and the normalized value is written back beside the raw one. That keeps the stored table readable by a human and lets normalization policy be re-versioned without touching a line of generator code.
The ontology got one stress test and passed. Three days after the decision log closed, the design took on a whole new asset class. Options cost two new source families and a handful of new signal kinds, and no schema change at all, because volatility surface information enters as ordinary scalar rows instead of a stored surface object. The existing decay, normalize and weight pipeline applied untouched, and equity consumers saw no new required fields.
The alternative is written down and rejected: bolting an asset-specific block onto the signal record would push an asset-class branch into every consumer and quietly change what the table is keyed by. The decision also names what has to happen first, since two new families dilute every existing weight, so a weight audit runs before anything gets switched on.
The other two decisions worth pointing at
Point-in-time correctness is a permission boundary, not a convention. The workers that generate signals and train models get a database role with no read access to the raw event tables at all. They can only see history through functions that apply the cutoff before any rows come back, so a caller can’t widen the filter by injecting a condition. The Python code sees an opaque handle and never a raw table, CI fails any change that imports a raw accessor into a worker, and a property test seeds future-dated rows and asserts none reach a generator. That’s the design. None of the three layers is built.
The action contract stops where the data stops. Trim and exit only mean something relative to a position’s size and cost basis, and Sophia doesn’t own consumer position state when it generates a signal. So the wire contract is two position-independent values, an entry stance and an exit pressure, and each bot turns those into its own portfolio actions. The SDK ships a default mapping to the familiar buy, hold, trim, exit labels, and the docs mark that helper illustrative rather than canonical, so the boundary doesn’t quietly leak back across the SDK.
The question above all of this
There’s an open question one level up from the design.
An April draft proposes reframing Sophia from a bottom-up aggregator into a top-down library of named, backtested patterns that fire when their conditions are met. It then stops at the admission policy, because whether a pattern gets admitted on hit rate, on expected payoff, or on the product of the two decides what kind of signal the system surfaces at all. A loose policy fails by surfacing junk. A strict one fails by never surfacing anything and never proving the thing works.
Six questions in that draft are unanswered. It’s filed as a draft instead of a decision, and the bottom-up design stands until it doesn’t.
Where it led
Working on Sophia is where several ideas now central to ageomaps first took shape: the infinite canvas, and pulling non-geospatial data into an analysis workspace purely for context. None of that is in this repo, since Sophia is specified as a headless service with no interface. But the problem is the same underneath. An analyst assembling a picture from sources that were never designed to sit next to each other, and needing the assembly itself to be inspectable.