Tickerbot is the stock market, in SQL. A trading algorithm is one WHERE clause on it: backtested with ?asof=, run live as a webhook.
Free plan. Every ticker, every signal, real-time data, all-time history.
the recipe
A trading algorithm is a condition and what you do when it fires. Write the condition in SQL, validate it against the past, run it live as a webhook — your handler decides, your broker executes.
1. Define the entry and exit condition — A SQL WHERE clause over 421+ precomputed columns: the whole market evaluated in one call, right now.
curl -X POST "https://api.tickerbot.io/v2/scan" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "q": "rsi_14 < 30 AND above_sma_200 AND market_cap > 1e9" }'{
"as_of": "2026-08-21T12:56:11Z",
"count": 2,
"results": [
{ "ticker": "LION", "name": "Lionsgate Studios Corp.",
"price": 11.50, "day_change_pct": 0.0031, "market_cap": 3445741094, … },
{ "ticker": "DVA", "name": "DaVita Inc.",
"price": 175.65, "day_change_pct": 0.0023, "market_cap": 11309826000, … }
]
}One q carries both sides: a ticker starting to match is your entry, a held ticker no longer matching is your exit. There is no separate exit condition to author.
2. Validate it historically — The same q with asof reruns it against the market as it was, delisted tickers included, unlimited depth.
const KEY = process.env.TICKERBOT_API_KEY
const Q = 'rsi_14 < 30 AND above_sma_200 AND market_cap > 1e9'
const dates = ['2024-01-05', '2024-02-05', '2024-03-05', '2024-04-05', '2024-05-06']
const positions = new Map() // ticker → entry price
for (const date of dates) {
// who matches, knowing only what this date knew — rows carry the price
const { results } = await fetch('https://api.tickerbot.io/v2/scan', {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ q: Q, asof: date }),
}).then((r) => r.json())
const matching = new Map(results.map((r) => [r.ticker, r.price]))
// EXITS — held positions no longer matching. An exited ticker is absent
// from the scan, so one series call prices the exits.
const exiting = [...positions.keys()].filter((t) => !matching.has(t))
if (exiting.length) {
const { series } = await fetch(
`https://api.tickerbot.io/v2/series?tickers=${exiting.join(',')}` +
`&columns=close&interval=1d&from=${date}&to=${date}`,
{ headers: { Authorization: `Bearer ${KEY}` } },
).then((r) => r.json())
for (const t of exiting) {
const close = (series[t] ?? [])[0]?.close
if (close) console.log(`${date} exit ${t} ${((close / positions.get(t) - 1) * 100).toFixed(1)}%`)
positions.delete(t)
}
}
// ENTRIES — matching tickers not yet held, priced by their own scan row
for (const [t, price] of matching) {
if (!positions.has(t) && price) positions.set(t, price)
}
}
// Real output for this q (condensed):
// 2024-02-05 exit AAPL 3.6% 2024-03-05 exit DUOL 18.4%
// 2024-02-05 exit MANH 25.0% 2024-03-05 exit AFL 5.9%
// 2024-02-05 exit KFRC 8.7% 2024-04-05 exit RAMP 8.4%
// 2024-02-05 exit LOPE 5.1% 2024-05-06 exit PFGC -1.1%
// 2024-02-05 exit WINA -2.1% 2024-05-06 exit NXT -6.3%This walk validates the exact rule the live loop trades: enter when a ticker matches (its scan row carries the entry price), exit when it no longer does; since an exited ticker is absent from the scan, one series call prices the exits. Entries and exits here follow the identical membership rule the webhook fires on. The full treatment (equity curve, position sizing) is the backtester guide.
3. Run it live — Subscribe the same query as a webhook: a ticker entering your condition is your signal to decide; your handler routes any order to your broker.
curl -X POST "https://api.tickerbot.io/v2/scan/subscribe" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "q": "rsi_14 < 30 AND above_sma_200 AND market_cap > 1e9",
"target_url": "https://your-bot.example.com/hook" }'{
"id": "wh_SVrlI0OowiE",
"q": "rsi_14 < 30 AND above_sma_200 AND market_cap > 1e9",
"channel": "webhook",
"target_url": "https://your-bot.example.com/hook",
"signing_secret": "whsec_…",
"status": "active",
…
}4. Write the algorithm — The delivery's matches are tickers that just entered the condition; current_matches is everything matching now — entries and exits from one delivery. (Payload lists cap at 100 rows; confirm any single position with GET /v2/tickers/{ticker}.)
import express from 'express'
const app = express()
app.use(express.json())
const positions = new Map() // ticker → qty: your book
const buy = async (ticker, qty) => { /* your broker's order API */ }
const sell = async (ticker, qty) => { /* your broker's order API */ }
app.post('/hook', async (req, res) => {
const { matches, current_matches } = req.body
const matching = new Set(current_matches.map((r) => r.ticker))
// ENTRIES — new matches you don't already hold
for (const row of matches) {
if (!positions.has(row.ticker)) {
await buy(row.ticker, 100)
positions.set(row.ticker, 100)
}
}
// EXITS — held positions whose entry condition no longer holds
for (const [ticker, qty] of positions) {
if (!matching.has(ticker)) {
await sell(ticker, qty)
positions.delete(ticker)
}
}
res.sendStatus(200)
})
app.listen(3000) // the server behind your target_urlCondition-exit is one exit style — thesis invalidation. Stops, targets, and time exits are your strategy logic, layered on the same book.
The calls behind it: POST /v2/scan · GET /v2/series · POST /v2/scan/subscribe
questions
The market as one computed table: 20,864+ tickers × 421+ signals, precomputed, refreshed continuously, and queried in SQL. Every read runs live, as of any past moment, or as a push. The main endpoints are /v2/tickers for one symbol’s full row, /v2/signals for every ticker matching a named signal, and /v2/scan for the whole market matching a SQL WHERE clause.
Every US-listed equity plus rates, FX and crypto, with the full schema of 421+ signals computed for each ticker. The data refreshes continuously, and all-time history sits behind every column.
A data API sells inputs (bars, ticks, statements) and leaves the derived values your product actually acts on for you to compute, refresh, and store, per symbol, continuously. Tickerbot sells the finished state: conditions like above_sma_200 are already columns, past answers are already reproducible, and the pipeline between raw data and product is the part you skip.
Computed state: hand a model raw data and its context window fills with math to do; hand it computed answers and the context goes to decisions. A scan returns the tickers matching your condition: a list, not a workload. Every call here is also a native tool call: install the MCP server and Claude, ChatGPT, Cursor, or any MCP runtime queries the market directly.
The Free plan needs no card and carries the full data side: every ticker, every signal, real-time data, all-time history and as-of queries, at 10,000 calls a month and 60 a minute. Paid plans start at $29/mo, remove the monthly cap, and raise the rate limit; webhooks and streaming come with them. Data depth is never a tier lever: every plan sees the same table.
Because every action it takes — an alert, a screen result, an order — is a condition over derived values: RSI below 30, price above the 200-day average, volume three times normal. Raw data doesn’t contain those. Something has to compute and refresh them for every symbol, continuously, and keep the history so past answers are reproducible. That’s a data pipeline, not a feature — you either build and operate it, or query one that already runs.
in the wild
What people are saying.
“dude. whoah.”
“Tickerbot is insane. It turns Claude into a quant.”
“Best value for hobbyists and advanced traders alike.”
get started
Free plan. Every ticker, every signal, real-time data, all-time history.