AI-driven news sentiment and breakout, in real time

Two scores, minute by minute, for the tickers you follow

AI-driven news sentiment and breakout in real time: Opearl scores every headline with its AI model and every hour bar with its breakout model, from −10 to +10, and serves the result as files you update once a minute, a live call, and alerts on Telegram or by mail.

Live, in three lines.

GET /v1/live/NVDA → the latest headline, scored.

GET /v1/backdata/TSLA.csv?after=… → the new minute rows.

/sub TSLA on @opearl_news_bot → every new headline in your Telegram.

For non-professional investors only.

Sub – · Host –

Overview

Four services on one host, https://news.opearl.ai, for a set of subscribed tickers.

Live

The latest headline about any ticker, scored on demand: score and headline in one JSON answer.

Sub, minute by minute

The whole history of a ticker as a CSV file, one row a minute, updated every minute with a link that returns only the new rows. Plus an hourly breakout file.

Create a ticker

Ask for a ticker that is not served yet: its archive is downloaded and scored, its files built, and it is updated from then on.

Alerts

Every new or revised headline of a ticker, pushed to a Telegram chat or a mailbox, with optional filters.

What is served today

TickerKindNews rows fromBreakout rows from
QQQ (also as TQQQ)Index ETF, scored from its constituents2016-01-042016-01-04
SOXX (also as SOXL)Index ETF, scored from its constituents2016-01-042016-01-04
TSLAStock2010-06-292010-09-23
BTCCrypto2021-02-092015-10-17
EURUSD, GBPUSD, USDJPYFX pairs2021-012016-01-04
ROKUStock (created on request)2017-09-282017-12

All times are New York time with their offset, for example 2026-10-17T10:44:00-04:00.

For non-professional investors only. The scores are AI readings of public headlines and prices. They are not investment advice and not an offer of any financial service.

Start with Telegram — no key needed

Open @opearl_news_bot in Telegram and press Start. Everything below works from the chat.

CommandWhat it does
/sub TSLAAlerts for TSLA on this chat: every new headline (item 1) and every revised headline (item 2), with the score, the headline and its link.
/unsub TSLAStop them.
/listWhat this chat follows.
/create AAPLBuild a ticker that is not served yet: the whole archive, the scores, the minute file, the breakout history. One creation runs at a time. The bot messages the chat when the ticker is ready and the chat follows it from then on. A mid-cap takes a few minutes, a large cap up to an hour.
/statusThe creation in progress: headlines downloaded, scored, percentage, time left.
/helpThe commands and your chat id.
A chat receives only the tickers it subscribed to. Single tickers only: stocks, coins and FX pairs. Index ETFs such as QQQ or SOXX have no alerts and cannot be created from the chat; they are built from their constituents.

The API

Base URL https://news.opearl.ai. Every route except /v1/ping and the alert confirmation needs your key.

X-API-Key: <your key>          # or:  Authorization: Bearer <your key>

A key in the URL is refused. Every error is one JSON envelope: {"error": {"code", "message", "retryable"}}.

RouteAnswers
GET /v1/pingPublic. ok, ready, and the minute job's lag in seconds.
GET /v1/live/{ticker}The latest headline of any ticker, scored now: score, headline, published, as_of, age_seconds, stale, cached. ?fields=score drops the headline, ?max_age=<seconds> refuses old news. For an FX pair the answer also carries tone and direction_sign.
GET /v1/backdataThe news files and their state: rows, history_from, history_to, data_version, update_url.
GET /v1/backdata/{ticker}.csvThe news history, one row a minute: time,score,item. With ?after= or ?since=, the update slice.
GET /v1/backdata/{ticker}/metaThe file's meta.
?res=hourOn /v1/live/{ticker}, /v1/backdata/{ticker}.csv, its /meta and the list: the same series on the hour (section Files and updates). Nothing is fetched or scored for it.
GET /v1/backdata/{etf}/constituents?asof=YYYY-MM-DDAn ETF's constituents and weights on a date; /composition.json gives the whole month-by-month history.
GET /v1/audit/{SYMBOL}.csvEvery headline ever scored for a company: published, score, qualifies, headline.
GET /v1/brkThe breakout files and their state.
GET /v1/brk/{ticker}.csvThe breakout history, one row an hour: hour,brk (an integer −10 … +10). Same update links.
GET /v1/brk/{ticker}/latestThe last breakout row as JSON; it changes once an hour.
GET /v1/brk/{ticker}/meta, GET /v1/brk/audit/{SYMBOL}.csvThe file's meta; the hour bars behind the breakout rows.
POST /v1/subs/{ticker}, GET /v1/subs/{ticker}/status, GET /v1/subs/{ticker}/events, GET /v1/subs, DELETE /v1/subs/{ticker}Create a ticker and watch it (section Create a ticker).
POST /v1/alerts, GET /v1/alerts, DELETE /v1/alerts/{id}, GET /v1/alerts/{id}/logAlerts (section Alerts).
GET /v1/healthJobs, lags, subs, disk: for monitoring.

Accepted ticker forms: BTC, BTC-USD, X:BTCUSD; EURUSD, EUR-USD, EUR/USD; TQQQ and SOXL answer the QQQ and SOXX data (same basket, the leverage is not applied).

Files and updates

Download a file once, then ask every minute for what is new. Nothing you hold ever changes.

The news file: one row a minute

time,score,item
2026-10-17T10:43:00-04:00,-2.00,0
2026-10-17T10:44:00-04:00,4.20,1
2026-10-17T10:45:00-04:00,4.20,0
  • The 10:44 row holds what was known at 10:43:50. The job runs at second 50 of every minute, writes the row for the next minute and appends it to the file in the same second; it is readable from 10:44:00.
  • item = 1 a new headline set this row, 0 carried forward (the same headline as the row before), 2 the current headline was revised and re-scored. Two headlines two minutes apart give two 1s; the same score from two different headlines gives two 1s. For an ETF, 1 means at least one constituent got a new headline that minute, 2 that one was revised.
  • Rows are never rewritten. A headline delivered late, or a revision, shows forward at the minute it was learned. The headline itself is in the live answer, in ?columns=extended and in the audit file, not in the file.
  • Grid: stocks and ETFs 04:00 … 19:59 New York on NYSE trading days (960 rows a day); BTC every minute; FX pairs every minute from Sunday 17:00 to Friday 16:59.

The same file on the hour: ?res=hour

GET /v1/backdata/TSLA.csv?res=hour

time,score,item
2026-10-17T10:00:00-04:00,-2.00,0
2026-10-17T11:00:00-04:00,4.20,1
  • For traders who work by the hour. The row labelled 11:00 is the 10:50 minute row: what was known at 10:49:50, and nothing later.
  • item = 1 a new headline arrived since the previous hour's reading, 2 only a revision did, 0 nothing.
  • One row is appended at every :50; the update link is the same with &res=hour, and X-Next-Run says when the next row is readable. Rows 05:00 … 20:00 for stocks and ETFs (readings 04:50 … 19:50), every hour for BTC, Sunday 18:00 to Friday 17:00 for FX.
  • It is a view of the minute file: nothing is fetched or scored for it, so the two can never disagree.

The breakout file: one row an hour

hour,brk
2026-10-17T10:00:00-04:00,0
2026-10-17T11:00:00-04:00,-3

The 11:00 row is the bar that closed at 10:50, built at :57. A 0 is the normal state: the score is non-zero only in an asset's most stretched hours. Rows 04:00 … 19:00 for stocks and ETFs, every hour for BTC, Sunday 17:00 to Friday 16:00 for FX.

Keeping a file up to date

  1. First time: GET /v1/backdata/QQQ.csv (or /v1/brk/QQQ.csv). Keep the X-Data-Version header and your last row's time. The body comes gzipped when you send Accept-Encoding: gzip; X-Content-SHA256 is its checksum; byte Range requests work for resumes.
  2. Then every minute from MM:00 (news), or every hour from HH:59 (breakout):
    GET /v1/backdata/QQQ.csv?after=2026-10-17T10:44:00-04:00&version=2
    200 → the rows strictly after your last one, one line a minute as a rule: append them. 204 → nothing newer yet (X-Next-Run says when). 409 version_changed → the history was regenerated on purpose: download the whole file again. ?since=<time> also exists and re-sends the row you name first, inclusive.
  3. Never derive after from the clock, always from the last row you hold.

Options on any file: ?columns=extended (the inputs behind each row: article id, publication time, headline; contributors, coverage and snapshot for an ETF; the bar close for the breakout file) and ?excel=1 (BOM, CRLF, formulas neutralised).

Sizes

A minute file holds two to four million rows: 70 to 130 MB plain, 5 to 10 MB gzipped. An hour file (?res=hour) holds 40 to 70 thousand rows, about 2 MB. An update slice is 40 to 80 bytes.

Live

GET /v1/live/{ticker} for any ticker, served or not: the newest headline tagged with it, scored at once.

{"ticker":"NVDA","score":4.2,"headline":"Nvidia shares rise on data-centre demand",
 "published":"2026-10-17T13:52:11Z","as_of":"2026-10-17T13:52:11Z","age_seconds":97,
 "stale":false,"cached":false,"source":"news","method":"tag"}

An index ETF has no single headline: its live value is its latest minute row, the mix over its constituents. An FX pair comes from the FX news source, with tone (the raw score) and direction_sign beside the signed score. There is no item in a live answer: nothing is kept between two calls.

GET /v1/live/{ticker}?res=hour answers the hourly reading of a served ticker instead: its latest :50 minute row, labelled with the hour it feeds (hour), with its item, the reading minute and next_hour, when the next one is due. It is read from the minute rows: nothing is fetched, nothing is scored.

The news score

Our AI model reads the headline and answers a sentiment from −10 to +10, with two decimals.

  • Only articles tagged with at most 5 tickers and whose title names fewer than 4 tickers count, so market round-ups never make a row. A reprint of the same headline does not reset the row.
  • Sources: institutional news wires for stocks, ETF constituents and crypto; specialised FX news desks for the currency pairs.
  • ETF score: the constituents' scores over the last 24 hours, mixed by their index weight; a name without a recent headline counts as neutral. Weights are the SEC-filed holdings in force that day, rolled by price between filings, so membership changes apply through the whole history.
  • FX pairs: the tone is signed for the pair's direction. A headline naming the pair or its base currency keeps its tone ("EUR/USD falls", "Euro climbs"); a headline about the quote currency alone is flipped ("Dollar surges" is negative for EUR/USD and GBP/USD, "Yen jumps" is negative for USD/JPY); a cross is signed by which side of it is ours.

The breakout score

An integer from −10 to +10 per hour, from the hour bars that close at :50 (extended hours 04:00–20:00 included; split-adjusted).

The score measures how far the price is stretched from its recent path, relative to the asset's own volatility, ranked against the asset's own last year. 0 is the normal state; the score is non-zero only in the asset's most stretched hours, and its sign is contrarian: a stretch up scores negative, a stretch down positive. For an ETF the stretch is mixed over the constituents by index weight and ranked against the basket's own year. A file starts about three months after its first bar, the history the ranking needs.

Create a ticker

Any single ticker: a US stock (AAPL), a coin (ETH or ETH-USD), an FX pair (AUDUSD or AUD/USD). Index ETFs built from constituents are refused with etf_needs_composition.

  1. POST /v1/subs/AAPL → 202 {job_id, status_url, events_url, csv_url}. One creation runs at a time: while one runs, another ticker gets 409 creation_busy with the running ticker and its ETA. A ticker that already exists answers 200 {state: "exists"}.
  2. Watch it: GET /v1/subs/AAPL/status (add ?wait=60 to long-poll) answers {state, step, articles, scored, pct, eta_s}, with state going queued → downloading → scoring → building → brk → done (or failed, with error). GET /v1/subs/AAPL/events is a text/event-stream of the same object every few seconds until done.
  3. done = the minute file is on disk and served at /v1/backdata/AAPL.csv, the breakout file at /v1/brk/AAPL.csv, the audit file at /v1/audit/AAPL.csv, and the ticker is updated every minute from then on. Alerts can be set on it. The whole archive is downloaded and scored: a mid-cap takes 3 to 10 minutes, a large cap up to an hour.
  4. DELETE /v1/subs/AAPL stops the updates (the file stays); GET /v1/subs lists the subs and the running job.

From Telegram the same thing is /create AAPL; the bot messages the chat when the ticker is ready.

Alerts

Single tickers only, never an ETF. On every minute row with item 1 or 2 the address receives the ticker, the minute, the score, the headline and its link.

POST /v1/alerts
{"ticker": "TSLA", "channel": "telegram", "address": "<chat id>", "min_abs_score": 5, "cooldown_s": 600}
  • Telegram: the bot gives the chat id on /start; or simply /sub TSLA in the chat, no API call needed. Active at once.
  • Email: "channel": "email", "address": "you@example.com"; the address receives a confirmation mail from alerts@opearl.ai and the alert is active once the link is clicked.
  • The two filters are optional: min_abs_score sends only when |score| reaches the value; cooldown_s sends at most one message per that many seconds.
  • GET /v1/alerts lists them, DELETE /v1/alerts/{id} removes one, GET /v1/alerts/{id}/log shows the last deliveries: sent, pending, or failed after three attempts.

Errors and limits

StatusCodeMeaning
401unauthorized, key_in_urlMissing or wrong key; a key in the URL.
404not_subscribed, no_news, no_jobNot a served ticker; no headline for it; no creation requested.
400bad_ticker, bad_since, version_required, etf_needs_composition, no_alerts_on_etfThe request itself.
409version_changed, creation_busy, alert_existsRe-download the file; a creation is running; the address already has that alert.
503busy, upstream_unavailable, service_restartingTry again after Retry-After seconds.

Updates: the news rows are readable from MM:00, the breakout rows from HH:59; X-Next-Run on every file answer says when the next come. One creation runs at a time. Mail alerts are limited to a few hundred a day in total; Telegram has no limit that matters.

Request a key

The API needs a key. Tell us who you are and what you want to do with the feeds; you get an answer by mail.

For non-professional investors only. Keys are issued to private individuals for their own use.