Build a trading algorithm

An algorithm is a condition and a loop. The condition side is one computed SQL table — every ticker × every signal — queryable in three tenses. Your strategy, your broker; the data layer is done.

The recipe

1. Define the entry condition

A SQL WHERE clause over 421+ precomputed columns: the whole market evaluated in one call, right now.

curl -s -X POST https://api.tickerbot.io/v2/scan \
  -H "Authorization: Bearer $TICKERBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "q": "rsi_14 < 30 AND above_sma_200 AND market_cap > 1e9" }'

2. Validate it historically

The same query with ?asof= reruns it against the market as it was — delisted tickers included, unlimited depth. Walk it across dates for a candidate history.

curl -s -X POST "https://api.tickerbot.io/v2/scan" \
  -H "Authorization: Bearer $TICKERBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "q": "rsi_14 < 30 AND above_sma_200 AND market_cap > 1e9",
        "asof": "2024-01-05" }'

The full walk-through is the backtester guide: candidate sets per date, trades priced from aligned series.

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 -s -X POST https://api.tickerbot.io/v2/scan/subscribe \
  -H "Authorization: Bearer $TICKERBOT_API_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" }'

4. Write the algorithm

The payload'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}.)

// POST from Tickerbot whenever the match set changes:
// { "event": "webhook.fired",
//   "q": "rsi_14 < 30 AND above_sma_200 AND market_cap > 1e9",
//   "as_of": "2026-08-14T15:05:00Z",
//   "matches":         [ { "ticker": "RIOT", "rsi_14": 27.4, ... } ],
//   "current_matches": [ /* every ticker matching right now */ ] }

app.post('/hook', async (req, res) => {
  const { matches, current_matches } = req.body
  const matching = new Set(current_matches.map((r) => r.ticker))

  // ENTRIES — tickers that just entered the condition.
  for (const row of matches) {
    if (!portfolio.has(row.ticker) && passesRisk(row)) {
      await broker.buy(row.ticker, positionSize(row))  // your broker's API
    }
  }

  // EXITS — held positions whose entry condition no longer holds.
  for (const pos of portfolio.positions()) {
    if (!matching.has(pos.ticker) && exitRules(pos)) {
      await broker.sell(pos.ticker, pos.qty)
    }
  }

  res.sendStatus(200)
})

Endpoints used

EndpointRole in this build
POST /v2/scanThe condition, evaluated across the whole market: live or ?asof=
POST /v2/scan/subscribeThe same condition as a push: fires on match-set changes
GET /v2/tickers/{ticker}Everything about one ticker in one row: position context
GET /v2/seriesAligned time series, up to 50 tickers × 25 columns

More guides