Basics
Market Oracle is a market data source only. It does not open trades or manage positions. Between the market and your strategy sits a single prepared layer: closed bars, indicators, order-book facts, macro context, and market regime.
closed: true). Each signal is processed once, deduplicated by the symbol:ts key.- Canonical candles are USDT spot. Cross-check mids via
GET /v1/quotes. CVD and minute book depend on/health.cvd.status. - One call to
GET /v1/context/{symbol}returns a ready decision snapshot instead of 8–10 requests. - Live notifications about closed bars arrive over WebSocket (
wss://…/v1/stream); the data itself comes via REST. HTTP webhooks are not supported: Oracle will notPOSTto your URL. Keep a socket and reconnect with backoff (see the examples). Without WS, pollGET /v1/latestevery 30–60 s. - Prices and indicators in JSON are decimal strings (no float errors). Use a decimal library for money math; convert to
numberonly for charts.
Quick start
Five minutes to your first meaningful API response:
1. Health
Service is up, data is fresh.
2. Plan
Your limits: requests per day, RPM, WS count.
3. Symbols
Take only pairs with ready=true.
4. Stream
Subscribe to closed bars.
5. Context
Decision snapshot: every 60–90 s and before entry.
6. Signal
One analysis per closed bar.
Connection
Two channels, two roles. REST pulls data (snapshots, history, calendar). WebSocket tells you when another bar closes. The trading loop is always the same: WS wakes you up → REST fetches the data → decide → wait. Oracle does not send webhooks (POST to your endpoint).
Address and key
Every request to /v1/* carries the header:
GET /healthis public, no key needed.- For WebSocket the key goes in the same
Authorizationheader, or — if your library cannot set WS headers — once in the URL:wss://api.market-oracle.pro/v1/stream?token=mo_…. TLS is on: always usehttps://andwss://. - Read the daily quota from
X-Quota-*after any REST call, or from JSONGET /v1/me. On HTTP429readretry_after_msin the body. - A Free key from this site has no expiry. Basic / Pro last one month, then renew.
What comes from where
| Need | Channel | Source |
|---|---|---|
| The "bar closed" moment | WebSocket | bar_close (bar + 1m indicators) |
| Everything for a decision in one response | REST | GET /v1/context/{symbol} |
| Trade cost and "too expensive / fine" | REST | trade_cost / cost_risk fields in context (not in WS) |
| Higher timeframe, live | REST | htf field in context (m15/h1/h4/d1; not in WS) |
| Candle and indicator history | REST | GET /v1/history / POST /v1/history/batch |
| Fresh book to validate an entry | REST | GET /v1/depth/{symbol}?seconds=3 |
| Minute order-book history | REST or WS | GET /v1/microstructure or the microstructure_close event |
| Event calendar, macro, market regime | REST | /v1/calendar, /v1/macro, /v1/market-regime |
WebSocket dialogue
/v1/history, then resume live.Symbol readiness
Not every listed pair is fit for analysis. Checking this flag bundle takes three seconds and saves you from trading on "warm" history:
| Field | Where | Meaning |
|---|---|---|
ready | symbols, status, context | true at ≥ 200 bars (EMA200 warmup) |
history | same | warming / ready / full_day (≥1440 bars) |
indicators_ready | latest, status, context | base set ema20+rsi14+atr14 present |
data_quality.score | context, status | 0…100; for entry require ≥ 70 |
lag_sec | status, context | age of the closed bar; >120 cuts score to ≤69 |
last_closed_ts | symbols, status | last closed bar time (ms UTC) |
ready ≠ indicators_ready. EMA200 strategies need both. Don't trade a pair with 15 candles as if it were BTC with a full day of history.GET /v1/context/{symbol}
The main endpoint for bots and agents: one response instead of 8–10 calls. Pull it every 60–90 s per active pair and always before entry.
| Block | What it is, in plain words |
|---|---|
bar + indicators | last closed 1m bar — the only signal source |
ticker | current price and 24h stats (for display and sizing, not for signals) |
depth | fresh book: spread, bid/ask skew, large levels |
trade_cost / cost_risk | what the trade costs and whether fees eat the stop (tradable: false = skip). Available only here, not in WS |
htf | higher bars, live: m15/h1/h4/d1 with indicators. No m5/m30/w1 — those only via history |
data_quality + freshness | whether to trust the snapshot: 0…100 score plus freshness of each component |
macro_event_soon | true = major event (FOMC/CPI) within 60 min — better pause |
derivatives + liquidations | funding, open interest, liquidation cascades — a filter, not a signal |
macro_snapshot | Fear & Greed, BTC dominance, stables, dollar index, 10Y yield |
cross_exchange | mid cross-check; divergence_warning = don't enter unchecked |
market_regime / volatility_regime | market backdrop: BTC correlation, volatility, breadth |
context_scope="live_snapshot", historical_safe=false — this is a current snapshot, not historical data for backtesting. Never blend it into a backtest.You can pass your own fees as query params — then tradable is computed for your cost model: ?fee_buy=0.001&fee_sell=0.001&slippage=0.0002&k_sl=2&rr=2&tradable_c_r_max=0.45. Backtest and live must use the same fees, otherwise the "expensive / fine" verdict diverges.
How to read trade_cost / cost_risk: fees + half-spread + slippage = c_spot (fraction of the trade, 0.0025 = 0.25% round-trip). Divide by the stop (k_sl × ATR) → c_r. A value of 0.45 means costs eat almost half a stop; p_be is the win rate needed to break even at your RR. tradable: false — the pair is too expensive for your stop: widen the stop or skip.
Response example (trimmed, same structure)
The full ~80-field JSON with every block explained lives in agents.md §7. Below is the skeleton returned by GET /v1/context/BTCUSDT:
score ≥ 70 + tradable: true + macro_event_soon: false = the snapshot is tradable. The signal comes only from bar (closed: true), everything else is filters.History & timeframes
Oracle maintains native higher-timeframe candles itself — don't stitch 5m/15m from minute bars on the client (you'd lose ADX, VWAP, CVD, OBV, MFI).
| Interval | Depth | Indicators |
|---|---|---|
1m | ~60 days | yes |
5m | 60 days | yes |
15m | 90 days | yes |
30m | 120 days | yes |
1h | ~9 months | yes |
4h | 2 years | yes |
1d | 3 years | yes |
1w | 5 years | yes |
- WS delivers closes for 1m only. A 15m loop: wait for the last minute of the 15m bucket via WS → fetch
history?interval=15m&limit=1(orcontext.htf.m15as a live filter) → trade only the closed bar, deduped by the higher bar's key. - For backtests, join the higher bar strictly as-of: the last bar with
ts <= decisionBar.ts. Never copy livehtfonto past bars. POST /v1/sync/bootstrapis a cold dump of 1m only (up to 10080 bars per pair). Call it only if a pair isn't warmed up; mid/HTF is covered by the background sync.- Cache history for at least 5 minutes — don't refetch it every minute.
Order book: live and history
Two different entities — don't mix them up:
GET /v1/depth (live) | Microstructure (history) | |
|---|---|---|
| What | current top-20, up to 10 seconds window | closed UTC minute: spread, imbalance, walls, pressure |
| Why | entry validation 1–2 s after the signal | backtests and book filters on history |
| Where | current window only, not stored as history | history + microstructure_close WS event |
| Call | /v1/depth/BTCUSDT?seconds=3 | /v1/microstructure/BTCUSDT / /latest |
sample_count ≥ 40, coverage_pct ≥ 66.7, quality.score ≥ 70, microstructure.ts == bar.ts, closed == true. Live depth is an execution filter — it never rewrites the historical signal of the closed bar.failover: true) candles may stay valid, but minute microstructure may stop and cvd.status go down. For book and CVD strategies a plain status == "ok" is not enough: check bars.status (candles) and cvd.status (flow) in GET /health.Indicators
The same set on 1m and on every higher TF after warmup. During warmup individual fields are null (don't substitute 0). Each group below is a ready input for a bot, entry signaler, or screener: trend, strength, momentum, volatility, volume, price anchor, candle quality, levels.
| Group | Keys | What it shows and how to use it |
|---|---|---|
| Trend | ema20 / ema50 / ema200, ema50_slope_pct | Direction and speed. Price above EMA200 — bullish backdrop; ema20/ema50 cross — momentum; |slope| ≤ 0.1 — grid-mode flat |
| Strength & direction | adx14, plus_di14 / minus_di14 / di_side | ADX is strength only (flat ≤25, trend ≥30), DI is the side (di_side = 1/-1/0). Trend bots trade only with ADX ≥ 25 |
| Momentum | rsi14, mfi14 | Overheat 0–100 (>70 overbought, <30 oversold). MFI is the volume-weighted second vote. Don't fade a trend on RSI alone |
| Volatility | atr14 / atr_pct, atr50 / atr_ratio_14_50, bb_mid / bb_upper / bb_lower / bb_width | Stop = k×ATR; ratio <0.8 — squeeze (energy builds), >1.2 — expansion; BB-width is the squeeze meter for breakouts |
| Volume & flow | vol_sma20, volume_usd / volume_rate, obv, cvd_delta | Confirm entries with ≥1.5–2× normal $/min (comparable across TFs). Cumulative CVD is a client-side sum of cvd_delta; valid only while cvd.status == ok |
| Price anchor | vwap | Session VWAP (resets at 00:00 UTC): above — buyers' day. On 1w it is not a weekly VWAP |
| Candle shape | body, wick_balance, close_pos, candle_q, range_ratio | Bar quality as one number candle_q; close_pos > 0.7 — strong long bar; range_ratio << 1 — noise, skip |
| Levels | swing_high / swing_low, last_swing_* | Strength-2 fractals with a 2-bar lag: stops beyond the extreme, targets, breakout entries. swing_* only on the confirmation bar |
Core readiness: indicators_ready means ema20+rsi14+atr14 are present. The rest arrives later: ADX/DI after ~27 bars, EMA200 after 200 bars of that interval, Bollinger after 20. A full reference for every key (formula, thresholds, bot usage, pitfalls) lives in agents.md. What Oracle will never have: MACD, Stochastic, Supertrend, Ichimoku — that's your strategy's logic.
How to combine the fields
Oracle data is inputs, not a ready-made buy/sell. Three working combinations for a bot, entry signaler, or screener:
Trend + HTF
On 1m bar_close check context.htf.h1: price above ema50, adx14 ≥ 25 and di_side == "1" — only longs on the lower TF. Below H1 EMA200 — longs only with breakout confirmation. Cuts entries against the higher trend.
Mean-reversion
On your TF: rsi14 left the zone (>70 down or <30 up), price returned to bb_mid / vwap, volume_rate at least normal, tradable, no news within 60 min. Without the quality gate skip the signal.
Screener + book
Every 5 min rank pairs by volume_rate and atr_ratio_14_50 (squeeze + volume). 1–2 s after a signal call GET /v1/depth?seconds=3: spread OK, imbalance with the trade, microstructure gate. No — skip; yes — limit at the wall.
cvd_delta on closed bars; backtests use history as-of, never live context.Plans
Three client plans: free, basic, pro. Prices live on the key-sales site; here are the limits so you can pick a plan for your bot. Your key's exact numbers are always in GET /v1/me: if they differ from the table, trust /v1/me.
| Plan | Requests / day | RPM | Min. pause | Rec. pause | Max WS | For |
|---|---|---|---|---|---|---|
free | 2,000 | 30 | 2000 ms | 2000 ms | 1 | learn the API, one bot |
basic | 5,000 | 60 | 200 ms | 1000 ms | 2 | live 24/7: 1–2 bots per key |
pro | 8,000 | 90 | 150 ms | 667 ms | 3 | live 24/7: up to 3 bots on one key |
- 1 HTTP request = 1 off the daily quota — regardless of how "heavy" the endpoint is. WebSocket frames and utility
/v1/me,/v1/symbols,/v1/meta/*,/v1/calendardon't spend it. - Two independent brakes on every REST call: a
min_interval_mspause between any requests and at mostrpmper sliding minute. Violations →429with aretry_after_mspause. Don't fire dozens of requests in parallel — use a queue with at least the recommended pause. - Parse the day remainder from
X-Quota-Remaining(after/v1/contextetc. — no extra request). Valueunlimitedmeans no cap;0— wait until unix timeX-Quota-Reset(UTC midnight).X-Quota-Costis1or0;X-Quota-Weightis load, not a second limit. Do not treatX-RateLimit-*as the daily remainder. - Several bots on one key share a single REST queue, while their WebSockets are independent. Need more processes than Max WS — get a second key.
- New keys from this site default to
freewith no expiry. Basic / Pro — one month, then renew.
basic — context every 60–90 s per active pair + always before entry; on free — roughly every ~30 min. latest for tickers — every 10–15 s, history — cached 5+ min. A bot without WS polling latest every 10 s burns ~8,640 requests a day — no plan covers that: switch to WebSocket first.Example headers after a billable GET /v1/context/BTCUSDT (names are case-insensitive, exposed in CORS):
GET /v1/me returns the same headers but does not spend daily quota. On 429 read Retry-After and JSON retry_after_ms; a normal 200 has no Retry-After. Full field table: agents.md, «Заголовки лимитов» / quota headers.
All endpoints
Service
| Method | Path | Description |
|---|---|---|
| GET | /health | service status and bar freshness (public) |
| GET | /v1/me | key plan, quota left, expires_at |
Symbols and status
| Method | Path | Description |
|---|---|---|
| GET | /v1/symbols | active pairs + bars / ready / history |
| GET | /v1/status/{symbol} | lag, ready, indicators, quality for one pair |
| GET | /v1/meta/{symbol} | tick / step / minNotional for order rounding |
History and sync
| Method | Path | Description |
|---|---|---|
| GET | /v1/history/{symbol} | ?interval=1m|5m|15m|30m|1h|4h|1d|1w, limit (up to 10080), from/to in ms |
| POST | /v1/history/batch | up to 20 pairs in one request, same interval set |
| POST | /v1/sync/bootstrap | cold dump of 1m only (up to 10080 bars per pair) |
| GET | /v1/latest?symbols= | last closed bar + ticker (up to 50 pairs; always read via .symbols[PAIR]) |
| GET | /v1/indicators/{symbol} | indicators only, 1m only (higher TFs — history) |
Market slice
| Method | Path | Description |
|---|---|---|
| GET | /v1/context/{symbol} | full decision snapshot |
| GET | /v1/depth/{symbol} | live top-20 (?seconds=0..10), execution filter |
| GET | /v1/derivatives/{symbol} | funding, OI, liquidations |
| GET | /v1/calendar | macro events: ?hours=, ?high_only=true, for backtests ?as_of=bar.ts&window_min=60 |
| GET | /v1/macro | Fear & Greed (+history ?fng_days=), dominance, stables, DXY/10Y |
| GET | /v1/quotes/{symbol} | BBO cross-check |
| GET | /v1/market-regime | BTC correlations, volatility, market breadth |
Microstructure
| Method | Path | Description |
|---|---|---|
| GET | /v1/microstructure/{symbol} | closed minute book history (interval=1m, up to 10080) |
| GET | /v1/microstructure/{symbol}/latest | last closed minute |
Code examples
Two starter clients — Python and Node.js. The node address is already filled in; add your mo_… key. Switch languages via tabs.
REST: first request and pre-entry gate
WebSocket: listen to closed bars
Errors
Check the HTTP status first, then the machine code field (equals status on errors) in the body. Never parse the error text.
| code / status | HTTP | When and what to do |
|---|---|---|
ok | 200 | success |
key_missing / key_invalid / key_expired / key_revoked | 401 | key problem: stop trading, ask the operator for a renewal |
too_fast / rate_limited | 429 | requesting too fast: wait retry_after_ms + jitter, then slow down |
quota_exceeded | 429 | daily quota spent: don't retry data REST until UTC midnight; WS and utility endpoints keep working |
ws_limit | 429* | too many WS connections per key: read code (status may be missing) |
unavailable | 503 | Oracle temporarily unavailable: backoff and retry |
Useful headers: daily remainder is X-Quota-* (not X-RateLimit-*); on 429 use Retry-After and JSON retry_after_ms. Unknown symbol / bad range — 400, internal error — 5xx with bounded exponential backoff.
Checklist for an AI agent / bot
GET /health→status == "ok"andbars.status == "ok"; for CVD strategies alsocvd.status == "ok".GET /v1/me→ account for plan, daily remainder,rpm,min_interval_ms,expires_at.GET /v1/symbols→ onlyready == true, preferfull_day.- WS subscribe (for the book —
microstructure: true); dedupe both event types bysymbol:ts. - On
bar_close— analyze once. 5m/15m/1h signals — only on the closed native bar of your TF. GET /v1/context/{symbol}on schedule and before entry; gates:score ≥ 70,lag ≤ 120,cost_risk.tradable, nomacro_event_soonordivergence_warning.- 1–2 s after the signal — optionally
GET /v1/depth?seconds=3as an execution filter. - After reconnect: pause ≥ min_interval →
/v1/history(+ microstructure) → exact join byts. - Don't recompute EMA/RSI/ADX on the client if Oracle already served them in
bar.indicators. Never publish yourmo_…key in public repos or frontend code.