EquationDB

← Back to all articles

We turn everything into a time series

12 October 2026 · EquationDB team

A market database collects very different things. Prices arrive every day. Revenue arrives four times a year, weeks after the quarter it describes. An insider purchase is one record with a name on it. An analyst upgrade is a sentence. A breadth reading is a number the database computes for itself. Each lives in its own dataset, with its own columns and its own statement to query it.

That is the right way to store them and, until now, the only way to read them. It also means the most natural research question — does this move with that? — needs a different piece of plumbing for every pair of things you want to compare.

So we gave everything a second form. Every data item in EquationDB is now also a time series, with one name and one shape, and any series can be put next to any other.

One shape

A series is a list of observations, and every observation has the same five parts:

PartMeaning
codethe series' name
datethe date the value describes
valuethe number
value_texta text value where there is one: a fiscal period, a firm and its rating, an insider's name
known_atthe date the value became knowable

A code is the entity, a dot, and the metric: AAPL.close, AAPL.revenue, AAPL.eps.surprise, AAPL.insiders.net_buy_value, AAPL.events.golden_cross, $TH.AI.close, us.breadth.pct_above_ma.pct_above_50d. If you can name it, you can read it:

SERIES AAPL.revenue

That returns Apple's quarterly revenue: the period end, the value, the fiscal period as text, and the day the statement was filed.

Anything next to anything

Put two codes in one statement and they come back as two columns on one time grid:

GET AAPL.revenue, MSFT.revenue SINCE 2015

The two companies' fiscal quarters end on different days. Each column simply holds its latest known value, so every row is a fair comparison.

The series do not have to come from the same dataset, or tick at the same pace:

SERIES AAPL.close, AAPL.revenue, AAPL.gross_margin SINCE 2020 ALIGN quarterly

A daily price, a quarterly statement line and a ratio computed from statements, read at quarter ends.

SERIES AAPL.close, MSFT.close, $TH.AI.close LAST 2y REBASE

Two stocks and a theme index on one scale, each starting at 100.

Two rules make this work. They are worth stating, because they are where most hand-built comparisons go wrong.

Rule one: nothing is visible before it was known

Revenue for the quarter that ended on 30 June did not exist on 30 June. It existed on the day the company filed, several weeks later. A chart that draws the number at the quarter end shows the price "reacting" to something nobody knew yet.

Every observation therefore carries known_at, and that is the date it enters a comparison. A statement line counts from its filing date. An insider trade counts from the day it was filed, not the day it was made. A dividend counts from its declaration. On a daily grid next to the price, the June quarter shows up in August:

SERIES AAPL.close, AAPL.revenue LAST 6mo

You can also ask for the world as it stood on a date. AS OF drops everything that was not yet known:

SERIES AAPL.revenue AS OF 2024-02-01

Where the source does not record when a value became known, we say so rather than pretend. Consensus estimates are the latest snapshot, and theme membership is today's classification; results that include them are marked as not point-in-time.

Rule two: levels hold, flows add up

A price is a level: it holds until the next one. Insider buying is a flow: things that happen, and that add up over a period. Putting them on one grid means treating them differently, and the series knows which it is.

SERIES AAPL.close, AAPL.insiders.sell_value LAST 1y ALIGN monthly

Each row is a month: the month's last close, and the month's total insider selling. On a daily grid the same flow is the amount on the day it was filed and zero on the others.

Does this move with that?

Once two series share a grid, comparing them is one more word:

SERIES CORR AAPL.close, SPY.close, $BRD.A50.close LAST 2y

That is the correlation of daily returns between a stock, the market and a breadth series: three pairs, three numbers. Prices are compared on their percentage change and flows on their totals, at the pace of the slower series, because a correlation of raw levels mostly measures that both went up.

Lead and lag is the same statement with a range of shifts:

SERIES CORR AAPL.insiders.net_buy_value, AAPL.close LAST 3y LAG 6

It compares monthly insider buying with the monthly return up to six months either side, and reads out the strongest: which series leads, and by how many periods.

One metric across the market

The other direction is one metric for many companies:

SERIES revenue_yoy IN top500 SORT DESC LIMIT 20

Each company's latest known revenue growth, with its own period end beside it. Add a date and you get the cross-section as it stood then, built only from statements that had been filed:

SERIES revenue_yoy IN sector('Technology') ON 2024-06-30 SORT DESC LIMIT 20

Finding series, and SQL

There are a great many series: every field and function for every symbol, every statement line for every company, every event for every stock. The catalog describes them by metric and tells you how many entities carry each one:

SHOW SERIES LIKE 'margin'
SHOW SERIES FOR AAPL

The second lists one company's actual series, each with its observation count and date range.

In SQL the same thing is a table called series:

SELECT date, value, known_at FROM series WHERE code = 'AAPL.revenue' AND date >= '2020-01-01'

The table is read by code and never scanned, so the query costs what that one series costs.

In the app, Explore › Series is the same idea with a search box: find series from any dataset, add them to one chart, switch between rebased and own-axis views, read the correlation and the lead–lag underneath, and open the statement in Query.

What we did not do

We did not copy the database into a second, series-shaped database. A series is resolved from the dataset that already holds it, through the same code that answers FINANCIALS or INSIDERS, so a series value and the dataset's value are the same number by construction, and adding the series form cost no storage. The one exception is optional: reading one metric for every company at once is faster from a compact, column-wise copy, which a database can switch on.

Why this matters beyond markets

EquationDB is an engine, and financial markets is the first database built on it. The series form is deliberately not about markets. An entity, a metric, a date, a value, an optional text and the time it became known describe a temperature reading at a weather station as well as they describe a revenue line. A code can carry a database prefix for exactly that reason. When a second database arrives, its series will sit on the same grid, under the same two rules, and the question "does this move with that?" will reach across databases as easily as it now reaches across datasets.

Start with SERIES, or open Explore › Series and pick two things you have always wanted to see side by side.