Tutorials
Short, real walkthroughs. Each one ends with working code you can drop into a project, not pseudocode. Three takes on the same API.
What every tutorial assumes
One-time setup so each walkthrough can stay focused.
- You have a Tickerbot API key. Get one from the dashboard; every plan starts with a 14-day free trial.
- You set
TICKERBOT_KEYin your environment. Tutorial code reads from there. Don't commit it. - You have Node 20+ (tutorials 1 and 2) or Python 3.10+ (tutorial 3) installed.
Momentum scanner in 20 lines
Build a live scanner that prints small-cap stocks gapping up on real volume, refreshed every minute. The whole thing is one /v2/scan call wrapped in a polling loop.
What you’ll build
A Node script that polls the API every 60 seconds and prints something like:
[2026-05-19T14:31:00.000Z] 4 matches
OWLT +14.2% 4.8× vol $81M
DJT +11.8% 3.2× vol $1840M
RIVN +9.2% 2.7× vol $1390M
SOUN +7.4% 2.1× vol $920M1. Compose the query
The interesting work is picking a WHERE clause that captures the setup you care about:
gap_up_3pct: opened at least 3% above prior closevolume_unusual_2x: trading at >= 2× the 10-day average volumemarket_cap < 2000000000: small-cap filterNOT earnings_this_week: skip earnings reactions
gap_up_3pct AND volume_unusual_2x AND market_cap < 2000000000 AND NOT earnings_this_weekSee the schema page for every flag and numeric column you can drop in.
2. The full script
// scanner.mjs — run with: TICKERBOT_KEY=tb_live_... node scanner.mjs
const QUERY = "gap_up_3pct AND volume_unusual_2x AND market_cap < 2000000000 AND NOT earnings_this_week";
async function scan() {
const url = new URL("https://api.tickerbot.io/v2/scan");
url.searchParams.set("q", QUERY);
url.searchParams.set("order", "day_change_pct");
url.searchParams.set("dir", "desc");
url.searchParams.set("limit", "20");
const r = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.TICKERBOT_KEY}` },
});
if (!r.ok) throw new Error(`scan failed: ${r.status}`);
return r.json();
}
function render({ as_of, results }) {
console.log(`[${as_of}] ${results.length} matches`);
for (const row of results) {
const pct = (row.day_change_pct * 100).toFixed(1);
const rv = row.relative_volume.toFixed(1);
const mc = (row.market_cap / 1e6).toFixed(0);
console.log(` ${row.ticker.padEnd(6)} +${pct}% ${rv}× vol $${mc}M`);
}
}
async function tick() {
try { render(await scan()); } catch (e) { console.error(e.message); }
}
await tick();
setInterval(tick, 60_000);20 lines including imports and the loop. Edit the query to change the strategy; everything else stays the same.
Discord breakout bot
Get a Discord ping every time a new ticker breaks out on volume. A Tickerbot webhook fires on the match, your receiver verifies the signature, and forwards a tidy message to a Discord channel.
What you’ll build
A 30-line Node service that sits between Tickerbot and Discord. When a ticker enters the match set, you get a message like this in your channel:
🚀 New breakouts:
NVDA +7.1% on 1.8× volume
AMD +5.2% on 1.4× volumeThe flow: Tickerbot evaluates the rule every minute → POSTs to your receiver when a new ticker matches → your receiver posts to Discord. No polling.
1. Create a Discord webhook
In Discord, right-click your channel, Edit Channel → Integrations → Webhooks → New Webhook. Name it, copy the URL. Save it as DISCORD_HOOK in your environment.
2. Create a Tickerbot webhook subscription
Below: any ticker that fires the breakout flag while sitting above its 200-day moving average. Replace the target URL with whatever public URL points at your receiver (use ngrok for local dev).
curl -X POST https://api.tickerbot.io/v2/webhooks \
-H "Authorization: Bearer $TICKERBOT_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "discord-breakouts",
"q": "breakout AND above_sma_200",
"target_url": "https://your-receiver.example.com/hook"
}'The response includes a one-shot signing_secret. Save it as TB_SECRET; it's only shown once. Every delivery is HMAC-signed with this so you can verify it came from us.
3. Write the receiver
A tiny Express server with one route. Two jobs: verify the signature, then forward to Discord.
// receiver.mjs
import express from "express";
import crypto from "crypto";
const app = express();
const DISCORD_HOOK = process.env.DISCORD_HOOK;
const TB_SECRET = process.env.TB_SECRET;
app.post("/hook", express.raw({ type: "application/json" }), async (req, res) => {
// 1) verify HMAC
const header = req.get("Tickerbot-Signature") || "";
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
const signed = crypto.createHmac("sha256", TB_SECRET)
.update(`${parts.t}.${req.body.toString()}`)
.digest("hex");
if (signed !== parts.v1) return res.status(401).end();
// 2) handle the event
const event = JSON.parse(req.body.toString());
if (event.event === "webhook.ping") return res.status(200).end();
if (event.event !== "webhook.fired") return res.status(200).end();
const lines = event.tickers.map(t => {
const pct = (t.day_change_pct * 100).toFixed(1);
const rv = t.relative_volume.toFixed(1);
return ` **${t.ticker}** +${pct}% on ${rv}× volume`;
});
await fetch(DISCORD_HOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content: `🚀 New breakouts:\n${lines.join("\n")}` }),
});
res.status(200).end();
});
app.listen(3000, () => console.log("listening on :3000"));The HMAC verification protects you from spoofed webhooks. See state-change deduplication for why you won’t get hammered with repeated events on the same ticker.
4. Expose it and test
For local dev, run ngrok to get a public URL, then copy that URL into the target_url of the webhook from step 2 (use PATCH /v2/webhooks/{id} if you need to update it). Tickerbot sends a one-shot webhook.ping on creation to verify reachability; once your receiver returns 200, the subscription flips to active and starts firing real events on matches.
Backtest a strategy over 2024
Run the same SQL WHERE clause across every trading day of 2024, collect the hits, then check each ticker’s 5-day forward return. The backtest is two API calls in a loop.
What you’ll build
A Python script that prints something like:
452 signal hits across 2024
5-day forward win rate: 58.4%
Median 5-day forward return: +1.2%
Best: NVDA on 2024-02-22 → +18.4%
Worst: SMCI on 2024-08-08 → -22.1%The trick: /v2/scan?asof=YYYY-MM-DD evaluates your same query against the historical state of the universe on that date. No CSV downloads, no point-in-time bookkeeping; the API handles it.
1. Pick the strategy
Same q as a live scan. Below: stocks coming out of oversold while sitting above their 200-day average and carrying real volume.
rsi_oversold AND above_sma_200 AND volume_unusual_2x AND market_cap > 50000000002. Walk every trading day, collect hits
import os, requests
from datetime import date, timedelta
KEY = os.environ["TICKERBOT_KEY"]
HEADERS = { "Authorization": f"Bearer {KEY}" }
QUERY = "rsi_oversold AND above_sma_200 AND volume_unusual_2x AND market_cap > 5000000000"
def scan_asof(d: date) -> list[dict]:
r = requests.get(
"https://api.tickerbot.io/v2/scan",
params={ "q": QUERY, "asof": d.isoformat(), "limit": 100 },
headers=HEADERS,
)
r.raise_for_status()
return r.json()["results"]
hits = []
day, end = date(2024, 1, 2), date(2024, 12, 31)
while day <= end:
if day.weekday() < 5: # skip weekends
for row in scan_asof(day):
hits.append({ "date": day, "ticker": row["ticker"], "entry": row["price"] })
day += timedelta(days=1)
print(f"{len(hits)} signal hits across 2024")~252 calls total — well under a paid plan's minute quota.
3. Check forward returns
def price_on_or_after(ticker: str, target: date) -> float | None:
r = requests.get(
f"https://api.tickerbot.io/v2/signals/price/{ticker}/history/1d",
params={ "from": target.isoformat(), "limit": 1 },
headers=HEADERS,
)
r.raise_for_status()
bars = r.json()["bars"]
return bars[0]["v"] if bars else None
returns = []
for h in hits:
exit_target = h["date"] + timedelta(days=7) # ~5 trading days
exit_price = price_on_or_after(h["ticker"], exit_target)
if exit_price is None: continue
returns.append((h["ticker"], h["date"], (exit_price - h["entry"]) / h["entry"]))4. Summarize
wins = sum(1 for _, _, r in returns if r > 0)
median = sorted(r for _, _, r in returns)[len(returns) // 2]
best = max(returns, key=lambda x: x[2])
worst = min(returns, key=lambda x: x[2])
print(f"5-day forward win rate: {wins / len(returns):.1%}")
print(f"Median 5-day forward return: {median:+.1%}")
print(f"Best: {best[0]} on {best[1]} → {best[2]:+.1%}")
print(f"Worst: {worst[0]} on {worst[1]} → {worst[2]:+.1%}")Plan history depth
To run a backtest over 2024 you need at least one full year of historical scan depth. Hobby covers 1 year, Pro covers 5, Scale and Enterprise cover all-time. If you go further back than your plan allows, the API returns history_window_exceeded with earliest_allowed_asof so your code can clamp.