The guide
This guide teaches the DEBYKO Query Language (DQL) by example. The generated API reference
(/v2 in Scalar) lists every endpoint field; the normative language definition is the
specification. This document is the tutorial and the cookbook.
Every response in this guide was produced by the engine. They are rendered from a small fixture (
DocumentationFixture) whose venue codes and symbols are the ones the platform really collects and whose prices are invented, and they are regenerated on every build — so the shape here is the shape you will get, field for field. What is made up is the numbers, never the form, and no page claims a price. The whole answers, including the canonical query each one echoes back, are inexamples.jsonbeside this file.
1. What DQL is for
Section titled “1. What DQL is for”DEBYKO stores what perpetual-futures venues published, when they published it, and when we received it. DQL is how you ask questions about that store:
- Screening — “which instruments satisfy this condition right now?” (
POST /v2/screen) - Screening in the past — the same question, answered with only what was known at a moment
you name (
as of) - Alerts — “call my webhook when this condition becomes true for an instrument”, with a tag
you choose in the payload (
POST /v2/rules)
A DQL query is a condition: a yes/no statement about data, written the way you would write a
WHERE clause. Indicators use the names you already know (ema, rsi, atr, crossover).
There are no variables, loops or scripts — a query is one expression, and the same expression has
an equivalent JSON form for programs.
Three things make DQL different from other screeners, and they come from how DEBYKO treats data:
- Venues stay separate. There is no “BTC price”; there is a BTC price on each venue. Every
condition about a venue value says which venues it is about:
any(...),all(...),count(...)ormax/min/sum(...). - Missing data stays missing. A venue that has not sent a funding rate has no funding rate — not zero, not yesterday’s. A condition on it is neither true nor false; it is unknown, and the language has rules for that (§6).
- Everything has an age and a source. Every value in every response tells you which venue sent it, when we received it and how old it was when the query used it.
2. Your first query
Section titled “2. Your first query”Question: which instruments have a positive funding rate on at least one venue?
any(venues where funding > 0)Read it as: for this instrument, is there any venue where funding is greater than zero?
funding on its own would not be a valid query — the language insists you say which venues.
Send it:
POST /v2/screenContent-Type: application/json
{ "query": "any(venues where funding > 0)", "limit": 2 }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [ { "instrument": "BTC-PERP", "result": "true", "values": {}, "venues": [ { "venue": "BYBIT-PERP", "symbol": "BTCUSDT", "quote": "USDT", "values": { "funding": { "value": 0.00002, "unit": "fraction", "venue": "BYBIT-PERP", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T11:52:00Z", "age_ms": 485000, "status": "stale" } } }, { "venue": "HYPERLIQUID", "symbol": "BTC", "quote": "USDC", "values": { "funding": { "value": 0.000013, "unit": "fraction", "venue": "HYPERLIQUID", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "KRAKEN-FUTURES", "symbol": "PF_XBTUSD", "quote": "USD", "values": { "funding": { "value": 0.00001, "unit": "fraction", "venue": "KRAKEN-FUTURES", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "OKX-PERP", "symbol": "BTC-USDT-SWAP", "quote": "USDT", "values": { "funding": { "value": 0.000054, "unit": "fraction", "venue": "OKX-PERP", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } } ] } ], "page": { "limit": 1, "returned": 1, "next_cursor": "eyJ0IjoiMjAyNi0wOS0yM1QxMjowMDowNVoiLCJhZnRlciI6IkJUQy1QRVJQIiwicSI6IjdlZjMxMGZmYTMyOTI2ZDEifQ" }}Things to notice:
resultis"true"for each returned instrument. Instruments where the answer is unknown are not returned unless you ask ("include_unknown": true).- Under each instrument, every listed venue appears, even the one whose value is missing
(OKX-PERP above). That is how you see why
anywas true: three venues said yes, one said nothing. - Every value carries
venue,received_at,age_msandstatus. There is no bare number anywhere in a DEBYKO response. next_cursorcontinues the same evaluation instant, so page two is consistent with page one even though the market moved.
3. Reading a value
Section titled “3. Reading a value”{ "value": 0.0001, "venue": "BYBIT-PERP", "layer": "funding", "venue_ts": "2026-09-21T09:30:03.517Z", "received_at": "2026-09-21T09:30:03.702Z", "age_ms": 1410, "status": "present" }| Key | Meaning |
|---|---|
value |
the number the query used; null when there was nothing to use |
venue |
which venue published it |
layer |
the data layer it came from (funding, mark, book, candles, …); fields in one layer share a timestamp |
venue_ts |
the venue’s own timestamp, when the venue sends one; null when it does not |
received_at |
when DEBYKO received it |
age_ms |
evaluated_at − received_at (or as_of − received_at) |
status |
present, stale, missing, not_published or off — see §6 |
reason |
only with a null value: no_value, not_retained, gap, insufficient_history, venue_capability, policy_off |
Indicator values add timeframe, bars (how many candles the number was computed from) and
candle_end (the close time of the newest candle used). Cross-venue aggregates (max, min,
sum) replace the single venue with a from list naming every venue that contributed.
Units, once:
- Prices are plain numbers in the venue’s quote currency (
quote), never converted. - Rates and spreads are fractions:
0.0001is one basis point,0.01is one percent. You may write them with a suffix —1bps,0.01%— and the language turns the suffix into the fraction.spread < 5bpsandspread < 0.0005are the same condition. fundingis the rate per funding interval as the venue publishes it; intervals differ (1h,4h,8h), sofunding_8his provided as the same rate scaled to eight hours.- Durations are
10s,2m,1h,1d(one unit, no1h30m).
3a. Saying what the query is about
Section titled “3a. Saying what the query is about”A condition says what must be true. A where clause says which listings to ask at all, and it is
the same clause on every endpoint — screen, snapshots, history, candles, books, stream. It runs first,
against the listing’s own identity, and never against a measurement:
venues_answered >= 1 where base = BTCvenues_answered >= 1 where instrument in (BTC-PERP, ETH-PERP)venues_answered >= 1 where venue = KRAKEN-FUTURESvenues_answered >= 1 where quote = USDTvenues_answered >= 1 where symbol = "BTCUSDT"Five things can be named there:
| In the clause | What it is | Written as |
|---|---|---|
instrument |
the market code DEBYKO normalised the listing to | a code: BTC-PERP |
base |
the base asset of that market | a code: BTC |
venue |
the venue’s published code | a code: KRAKEN-FUTURES, from GET /v2/venues |
quote |
the listing’s quote currency | a code: USDT |
symbol |
the venue’s own name for the listing | a quoted string: "BTCUSDT" |
A selection may stand alone: where base = BTC with no condition means “every listing of BTC”, which
is what the data endpoints send. A venue code that no longer exists, or an instrument that never did,
is an error naming what is known — never an empty answer that looks like “nothing matched”.
Venue codes come from GET /v2/venues, which needs no key:
{ "evaluated_at": "2026-09-23T08:51:50.944Z", "items": [ { "venue": "KRAKEN-FUTURES", "name": "Kraken Futures", "kind": "order_book", "listings": 296, "listings_collected": 5, "since": "2026-09-20T15:11:19.524Z" } ] }listings is what the venue lists; listings_collected is what DEBYKO collects today. The two differ
on purpose, and the difference is not hidden.
3b. Units, and what a value says about itself
Section titled “3b. Units, and what a value says about itself”A venue publishes what it publishes: one exchange counts open interest in contracts, another in the base asset. DQL does not guess. You ask for the units you want, and every value tells you what it is.
"units": "published"(the default) — exactly what the venue published."units": "base"— converted to base-asset units with the listing’s multiplier and contract size.
Every value in every response carries unit: quote, base, contracts, fraction, count or
duration. Nothing has to be assumed from a field name.
Two consequences worth knowing before they surprise you:
- Summing a quantity across venues (
sum(venues, oi)) is refused underunits: published— the sum of Bybit contracts and Kraken contracts is not a number anybody should print. Ask forunits: baseand it is allowed. - Under
units: base, a listing whose multiplier or contract size DEBYKO does not know answers null with reasonunit_unknownrather than an unconverted number that looks right.
Three response options, while you are here:
"number_format": "string"returns every number as the venue’s own digits in a JSON string, so a price that does not fit a double survives the round trip."json"(the default) returns numbers."include_raw": trueadds the venue’s untouched payload for each layer beside the parsed values.items_sha256comes back on every data answer: the hash of the items as serialised. Two answers with the same hash are the same answer, which is cheaper to check than a diff.
4. Cookbook
Section titled “4. Cookbook”Each entry gives the intent, the string form, the JSON form, a request and a rendered response. Responses are shortened to the instruments and venues that make the point; a real response lists every venue of every returned instrument.
4.1 Positive funding on any venue
Section titled “4.1 Positive funding on any venue”Intent: instruments with a positive funding rate on at least one venue.
any(venues where funding > 0)Covered in §2, including the JSON form and the full response.
4.2 Fresh positive funding on at least three venues
Section titled “4.2 Fresh positive funding on at least three venues”Intent: the funding rate, normalised to eight hours, is above one basis point on three or more venues, and each of those readings is less than two minutes old.
count(venues where funding_8h > 1bps and age(funding) < 2m) >= 3JSON form:
{ "dql": "1", "condition": { "type": "cmp", "op": ">=", "left": { "type": "count", "where": { "type": "and", "args": [ { "type": "cmp", "op": ">", "left": { "type": "field", "name": "funding_8h" }, "right": { "type": "number", "value": 0.0001 } }, { "type": "cmp", "op": "<", "left": { "type": "call", "name": "age", "args": [ { "type": "field", "name": "funding" } ] }, "right": { "type": "duration", "value": "2m" } } ] } }, "right": { "type": "number", "value": 3 } } }Request:
{ "query": "count(venues where funding_8h > 1bps and age(funding) < 2m) >= 3", "limit": 50 }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [], "page": { "limit": 1, "returned": 0, "next_cursor": null }}DYDX-PERP is listed but did not count: its rate is below the threshold. Three venues did, so the
condition holds. Note the canonical string in query.string: 1bps became 0.0001.
4.3 Cross-venue mark dispersion
Section titled “4.3 Cross-venue mark dispersion”Intent: the highest and lowest mark price across venues differ by more than five basis points of the lowest, and at least three venues are answering.
(max(venues, mark) - min(venues, mark)) / min(venues, mark) > 5bps and venues_answered >= 3JSON form:
{ "dql": "1", "condition": { "type": "and", "args": [ { "type": "cmp", "op": ">", "left": { "type": "arith", "op": "/", "left": { "type": "arith", "op": "-", "left": { "type": "agg", "fn": "max", "expr": { "type": "field", "name": "mark" } }, "right": { "type": "agg", "fn": "min", "expr": { "type": "field", "name": "mark" } } }, "right": { "type": "agg", "fn": "min", "expr": { "type": "field", "name": "mark" } } }, "right": { "type": "number", "value": 0.0005 } }, { "type": "cmp", "op": ">=", "left": { "type": "field", "name": "venues_answered" }, "right": { "type": "number", "value": 3 } } ] } }Request:
{ "query": "(max(venues, mark) - min(venues, mark)) / min(venues, mark) > 5bps and venues_answered >= 3" }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [ { "instrument": "ETH-PERP", "result": "true", "values": { "max(venues, mark)": { "value": 2751, "unit": "quote", "from": [ { "venue": "BYBIT-PERP", "value": 2751, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } ] }, "min(venues, mark)": { "value": 2749, "unit": "quote", "from": [ { "venue": "OKX-PERP", "value": 2749, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } ] }, "venues_answered": { "value": 3, "from": "reference", "as_of": "2026-09-23T12:00:05Z" } }, "venues": [ { "venue": "BYBIT-PERP", "symbol": "ETHUSDT", "quote": "USDT", "values": { "mark": { "value": 2751, "unit": "quote", "venue": "BYBIT-PERP", "layer": "mark", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "KRAKEN-FUTURES", "symbol": "PF_ETHUSD", "quote": "USD", "values": { "mark": { "value": 2750, "unit": "quote", "venue": "KRAKEN-FUTURES", "layer": "mark", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "OKX-PERP", "symbol": "ETH-USDT-SWAP", "quote": "USDT", "values": { "mark": { "value": 2749, "unit": "quote", "venue": "OKX-PERP", "layer": "mark", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } } ] } ], "page": { "limit": 1, "returned": 1, "next_cursor": null }}Dispersion is (0.8134 − 0.8119) / 0.8119 ≈ 18.5 bps. KRAKEN-FUTURES had a mark, but it was
84 seconds old — older than the 30-second freshness bound — so it was stale, excluded from
max/min, and not counted in venues_answered. The response shows the value’s age; it does not
pretend the value is current. Also note that max/min provenance names the winning venue.
4.4 Two named venues
Section titled “4.4 Two named venues”Intent: the mark price on KRAKEN-FUTURES and BYBIT-PERP differs by more than 10 (quote units).
max(venues where venue in (KRAKEN-FUTURES, BYBIT-PERP), mark) - min(venues where venue in (KRAKEN-FUTURES, BYBIT-PERP), mark) > 10JSON form:
{ "dql": "1", "condition": { "type": "cmp", "op": ">", "left": { "type": "arith", "op": "-", "left": { "type": "agg", "fn": "max", "expr": { "type": "field", "name": "mark" }, "where": { "type": "in", "left": { "type": "field", "name": "venue" }, "values": [ { "type": "code", "value": "KRAKEN-FUTURES" }, { "type": "code", "value": "BYBIT-PERP" } ] } }, "right": { "type": "agg", "fn": "min", "expr": { "type": "field", "name": "mark" }, "where": { "type": "in", "left": { "type": "field", "name": "venue" }, "values": [ { "type": "code", "value": "KRAKEN-FUTURES" }, { "type": "code", "value": "BYBIT-PERP" } ] } } }, "right": { "type": "number", "value": 10 } } }Request:
{ "query": "max(venues where venue in (KRAKEN-FUTURES, BYBIT-PERP), mark) - min(venues where venue in (KRAKEN-FUTURES, BYBIT-PERP), mark) > 10" }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [ { "instrument": "SOL-PERP", "result": "unknown", "values": { "max(venues where venue in (KRAKEN-FUTURES, BYBIT-PERP), mark)": { "value": null, "unit": "quote", "from": [], "reason": "no_value" }, "min(venues where venue in (KRAKEN-FUTURES, BYBIT-PERP), mark)": { "value": null, "unit": "quote", "from": [], "reason": "no_value" } }, "venues": [ { "venue": "HYPERLIQUID", "symbol": "SOL", "quote": "USDC", "values": { "venue": { "value": "HYPERLIQUID", "venue": "HYPERLIQUID", "layer": "reference", "venue_ts": null, "received_at": "2026-09-23T00:00:00Z", "age_ms": 43205000, "status": "present" }, "mark": { "value": 118, "unit": "quote", "venue": "HYPERLIQUID", "layer": "mark", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } } ] } ], "page": { "limit": 1, "returned": 1, "next_cursor": null }}The where inside max(...) restricts the venues the aggregate looks at; other venues still
appear in the response because they are part of the instrument, but they did not contribute.
4.5 Tight books wherever there is a book
Section titled “4.5 Tight books wherever there is a book”Intent: every venue that publishes an order book has a spread below five basis points. Venues that publish no book (oracle-priced venues) must not make the condition fail.
all(venues where status(bid) = not_published or spread < 5bps)JSON form:
{ "dql": "1", "condition": { "type": "all", "where": { "type": "or", "args": [ { "type": "cmp", "op": "=", "left": { "type": "call", "name": "status", "args": [ { "type": "field", "name": "bid" } ] }, "right": { "type": "status", "value": "not_published" } }, { "type": "cmp", "op": "<", "left": { "type": "field", "name": "spread" }, "right": { "type": "number", "value": 0.0005 } } ] } } }Request:
{ "query": "all(venues where status(bid) = not_published or spread < 5bps)" }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [ { "instrument": "BTC-PERP", "result": "true", "values": {}, "venues": [ { "venue": "BYBIT-PERP", "symbol": "BTCUSDT", "quote": "USDT", "values": { "status(bid)": { "value": "present", "venue": "BYBIT-PERP", "layer": "book", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" }, "spread": { "value": 1.1495574203931486e-05, "unit": "fraction", "venue": "BYBIT-PERP", "layer": "book", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "HYPERLIQUID", "symbol": "BTC", "quote": "USDC", "values": { "status(bid)": { "value": "present", "venue": "HYPERLIQUID", "layer": "book", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" }, "spread": { "value": 2.2985863693828294e-05, "unit": "fraction", "venue": "HYPERLIQUID", "layer": "book", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "KRAKEN-FUTURES", "symbol": "PF_XBTUSD", "quote": "USD", "values": { "status(bid)": { "value": "present", "venue": "KRAKEN-FUTURES", "layer": "book", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" }, "spread": { "value": 2.2988505747126437e-05, "unit": "fraction", "venue": "KRAKEN-FUTURES", "layer": "book", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "OKX-PERP", "symbol": "BTC-USDT-SWAP", "quote": "USDT", "values": { "status(bid)": { "value": "present", "venue": "OKX-PERP", "layer": "book", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" }, "spread": { "value": 2.298982700289e-06, "unit": "fraction", "venue": "OKX-PERP", "layer": "book", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } } ] } ], "page": { "limit": 1, "returned": 1, "next_cursor": "eyJ0IjoiMjAyNi0wOS0yM1QxMjowMDowNVoiLCJhZnRlciI6IkJUQy1QRVJQIiwicSI6ImI5ZTY0YzFjZWJmOTkzZmQifQ" }}Without the status(bid) = not_published or part, a venue that publishes no book would make the
all unknown (they have no spread, so spread < 5bps is neither true nor false) and ETH would
not be returned. §6 explains why.
4.6 Trend on hourly candles, any venue
Section titled “4.6 Trend on hourly candles, any venue”Intent: on at least one venue, the 20-candle EMA of hourly closes is above the 50-candle EMA.
any(venues where ema(close, 20) > ema(close, 50)) timeframe 1hJSON form:
{ "dql": "1", "timeframe": "1h", "condition": { "type": "any", "where": { "type": "cmp", "op": ">", "left": { "type": "call", "name": "ema", "args": [ { "type": "source", "name": "close" }, { "type": "number", "value": 20 } ] }, "right": { "type": "call", "name": "ema", "args": [ { "type": "source", "name": "close" }, { "type": "number", "value": 50 } ] } } } }Request:
{ "query": "any(venues where ema(close, 20) > ema(close, 50)) timeframe 1h", "limit": 20 }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "timeframe": "1h", "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [ { "instrument": "BTC-PERP", "result": "unknown", "values": {}, "venues": [ { "venue": "BYBIT-PERP", "symbol": "BTCUSDT", "quote": "USDT", "values": { "ema(close, 20)": { "value": null, "unit": "quote", "venue": "BYBIT-PERP", "layer": "candles", "timeframe": "1h", "bars": 80, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "ema(close, 50)": { "value": null, "unit": "quote", "venue": "BYBIT-PERP", "layer": "candles", "timeframe": "1h", "bars": 200, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" } } }, { "venue": "HYPERLIQUID", "symbol": "BTC", "quote": "USDC", "values": { "ema(close, 20)": { "value": null, "unit": "quote", "venue": "HYPERLIQUID", "layer": "candles", "timeframe": "1h", "bars": 80, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "ema(close, 50)": { "value": null, "unit": "quote", "venue": "HYPERLIQUID", "layer": "candles", "timeframe": "1h", "bars": 200, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" } } }, { "venue": "KRAKEN-FUTURES", "symbol": "PF_XBTUSD", "quote": "USD", "values": { "ema(close, 20)": { "value": null, "unit": "quote", "venue": "KRAKEN-FUTURES", "layer": "candles", "timeframe": "1h", "bars": 80, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "ema(close, 50)": { "value": null, "unit": "quote", "venue": "KRAKEN-FUTURES", "layer": "candles", "timeframe": "1h", "bars": 200, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" } } }, { "venue": "OKX-PERP", "symbol": "BTC-USDT-SWAP", "quote": "USDT", "values": { "ema(close, 20)": { "value": null, "unit": "quote", "venue": "OKX-PERP", "layer": "candles", "timeframe": "1h", "bars": 80, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "ema(close, 50)": { "value": null, "unit": "quote", "venue": "OKX-PERP", "layer": "candles", "timeframe": "1h", "bars": 200, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" } } } ] } ], "page": { "limit": 1, "returned": 1, "next_cursor": "eyJ0IjoiMjAyNi0wOS0yM1QxMjowMDowNVoiLCJhZnRlciI6IkJUQy1QRVJQIiwicSI6ImRkOWI3MzhhMWQzNzY1OTQifQ" }}Two details. First, the candle used is the last completed hourly candle (candle_end
09:00); the candle that is still forming is never used, so a condition cannot become true and
then untrue within the same hour as the price wobbles. Second, ema(close, 20) uses exactly 80
candles (bars), and ema(close, 50) exactly 200 — a fixed window, defined in the
specification, so you can recompute the number yourself. WEEX-FUTURES had a hole somewhere in
its last 200 hourly candles, so both EMAs are null there (reason: gap); DEBYKO does not fill
holes, and the other two venues carried the condition.
4.7 A cross on the latest closed 15-minute candle, one venue, clean history
Section titled “4.7 A cross on the latest closed 15-minute candle, one venue, clean history”Intent: on KRAKEN-FUTURES, the 20-EMA crossed above the 50-EMA on the most recent completed 15-minute candle, and the last 200 candles have no gaps.
any(venues where venue = KRAKEN-FUTURES and crossover(ema(close, 20), ema(close, 50)) and gaps(200) = 0) timeframe 15mJSON form:
{ "dql": "1", "timeframe": "15m", "condition": { "type": "any", "where": { "type": "and", "args": [ { "type": "cmp", "op": "=", "left": { "type": "field", "name": "venue" }, "right": { "type": "code", "value": "KRAKEN-FUTURES" } }, { "type": "call", "name": "crossover", "args": [ { "type": "call", "name": "ema", "args": [ { "type": "source", "name": "close" }, { "type": "number", "value": 20 } ] }, { "type": "call", "name": "ema", "args": [ { "type": "source", "name": "close" }, { "type": "number", "value": 50 } ] } ] }, { "type": "cmp", "op": "=", "left": { "type": "call", "name": "gaps", "args": [ { "type": "number", "value": 200 } ] }, "right": { "type": "number", "value": 0 } } ] } } }Request:
{ "query": "any(venues where venue = KRAKEN-FUTURES and crossover(ema(close, 20), ema(close, 50)) and gaps(200) = 0) timeframe 15m" }Illustrative response (only the named venue is shown; the others are listed in a real response
with venue = KRAKEN-FUTURES evaluating to false):
{ "dql": "1", "query": { "string": "any(venues where venue = KRAKEN-FUTURES and crossover(ema(close, 20), ema(close, 50)) and gaps(200) = 0) timeframe 15m", "json": { "…": "…" } }, "evaluated_at": "2026-09-21T09:30:05.112Z", "as_of": null, "settled": false, "timeframe": "15m", "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [ { "instrument": "DOGE", "result": "true", "values": {}, "venues": [ { "venue": "KRAKEN-FUTURES", "symbol": "DOGEUSDT", "quote": "USDT", "values": { "venue": { "value": "KRAKEN-FUTURES", "venue": "KRAKEN-FUTURES", "layer": "reference", "venue_ts": null, "received_at": "2026-08-30T00:00:12.000Z", "age_ms": 1934393112, "status": "present" }, "crossover(ema(close, 20), ema(close, 50))": { "value": true, "venue": "KRAKEN-FUTURES", "layer": "candles", "timeframe": "15m", "bars": 201, "candle_end": "2026-09-21T09:30:00Z", "venue_ts": "2026-09-21T09:30:00Z", "received_at": "2026-09-21T09:30:00.455Z", "age_ms": 4657, "status": "present" }, "gaps(200)": { "value": 0, "venue": "KRAKEN-FUTURES", "layer": "candles", "timeframe": "15m", "bars": 200, "candle_end": "2026-09-21T09:30:00Z", "venue_ts": "2026-09-21T09:30:00Z", "received_at": "2026-09-21T09:30:00.455Z", "age_ms": 4657, "status": "present" } } } ] } ], "page": { "limit": 100, "returned": 1, "next_cursor": null }}crossover(a, b) means: on the newest completed candle a > b, and on the one before it
a <= b. It is a statement about two candles, not a forecast.
4.8 Two timeframes in one condition
Section titled “4.8 Two timeframes in one condition”Intent: hourly trend up (EMA 20 above EMA 50 on 1h candles) while the 15-minute RSI is low.
any(venues where ema(close, 20, 1h) > ema(close, 50, 1h) and rsi(close, 14, 15m) < 35)JSON form:
{ "dql": "1", "condition": { "type": "any", "where": { "type": "and", "args": [ { "type": "cmp", "op": ">", "left": { "type": "call", "name": "ema", "args": [ { "type": "source", "name": "close" }, { "type": "number", "value": 20 } ], "timeframe": "1h" }, "right": { "type": "call", "name": "ema", "args": [ { "type": "source", "name": "close" }, { "type": "number", "value": 50 } ], "timeframe": "1h" } }, { "type": "cmp", "op": "<", "left": { "type": "call", "name": "rsi", "args": [ { "type": "source", "name": "close" }, { "type": "number", "value": 14 } ], "timeframe": "15m" }, "right": { "type": "number", "value": 35 } } ] } } }Request:
{ "query": "any(venues where ema(close, 20, 1h) > ema(close, 50, 1h) and rsi(close, 14, 15m) < 35)" }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [ { "instrument": "BTC-PERP", "result": "unknown", "values": {}, "venues": [ { "venue": "BYBIT-PERP", "symbol": "BTCUSDT", "quote": "USDT", "values": { "ema(close, 20, 1h)": { "value": null, "unit": "quote", "venue": "BYBIT-PERP", "layer": "candles", "timeframe": "1h", "bars": 80, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "ema(close, 50, 1h)": { "value": null, "unit": "quote", "venue": "BYBIT-PERP", "layer": "candles", "timeframe": "1h", "bars": 200, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "rsi(close, 14, 15m)": { "value": null, "venue": "BYBIT-PERP", "layer": "candles", "timeframe": "15m", "bars": 57, "candle_end": "2026-09-23T12:00:00Z", "venue_ts": null, "received_at": "2026-09-23T12:00:00.300Z", "age_ms": 4700, "status": "present", "reason": "gap" } } }, { "venue": "HYPERLIQUID", "symbol": "BTC", "quote": "USDC", "values": { "ema(close, 20, 1h)": { "value": null, "unit": "quote", "venue": "HYPERLIQUID", "layer": "candles", "timeframe": "1h", "bars": 80, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "ema(close, 50, 1h)": { "value": null, "unit": "quote", "venue": "HYPERLIQUID", "layer": "candles", "timeframe": "1h", "bars": 200, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "rsi(close, 14, 15m)": { "value": 88.19612724877551, "venue": "HYPERLIQUID", "layer": "candles", "timeframe": "15m", "bars": 57, "candle_end": "2026-09-23T12:00:00Z", "venue_ts": null, "received_at": "2026-09-23T12:00:00.300Z", "age_ms": 4700, "status": "present" } } }, { "venue": "KRAKEN-FUTURES", "symbol": "PF_XBTUSD", "quote": "USD", "values": { "ema(close, 20, 1h)": { "value": null, "unit": "quote", "venue": "KRAKEN-FUTURES", "layer": "candles", "timeframe": "1h", "bars": 80, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "ema(close, 50, 1h)": { "value": null, "unit": "quote", "venue": "KRAKEN-FUTURES", "layer": "candles", "timeframe": "1h", "bars": 200, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "rsi(close, 14, 15m)": { "value": 88.19612724877551, "venue": "KRAKEN-FUTURES", "layer": "candles", "timeframe": "15m", "bars": 57, "candle_end": "2026-09-23T12:00:00Z", "venue_ts": null, "received_at": "2026-09-23T12:00:00.300Z", "age_ms": 4700, "status": "present" } } }, { "venue": "OKX-PERP", "symbol": "BTC-USDT-SWAP", "quote": "USDT", "values": { "ema(close, 20, 1h)": { "value": null, "unit": "quote", "venue": "OKX-PERP", "layer": "candles", "timeframe": "1h", "bars": 80, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "ema(close, 50, 1h)": { "value": null, "unit": "quote", "venue": "OKX-PERP", "layer": "candles", "timeframe": "1h", "bars": 200, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" }, "rsi(close, 14, 15m)": { "value": null, "venue": "OKX-PERP", "layer": "candles", "timeframe": "15m", "bars": 57, "candle_end": null, "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" } } } ] } ], "page": { "limit": 1, "returned": 1, "next_cursor": "eyJ0IjoiMjAyNi0wOS0yM1QxMjowMDowNVoiLCJhZnRlciI6IkJUQy1QRVJQIiwicSI6IjMxOTVhZGUxMDU2YTFhOWMifQ" }}When every series function carries its own timeframe, the timeframe clause is not needed. Mixing
is fine as long as each function’s series is consistent: crossover(ema(close, 20, 1h), ema(close, 50, 4h)) is rejected because the two series have different timeframes.
4.9 Which instruments have a venue that has gone quiet
Section titled “4.9 Which instruments have a venue that has gone quiet”Intent: instruments where at least one listed venue is not answering (no fresh mark price).
venues_answered < venues_listedJSON form:
{ "dql": "1", "condition": { "type": "cmp", "op": "<", "left": { "type": "field", "name": "venues_answered" }, "right": { "type": "field", "name": "venues_listed" } } }Request:
{ "query": "venues_answered < venues_listed", "limit": 3 }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [], "page": { "limit": 1, "returned": 0, "next_cursor": null }}This query references no venue field, so per-venue values are empty; add status(mark) inside a
quantifier if you want to see which venue is quiet (next entry).
4.10 Stale, missing and not published are three different things
Section titled “4.10 Stale, missing and not published are three different things”Intent: instruments where at least one venue’s funding rate is stale — we have a value, but it is older than the freshness bound.
count(venues where status(funding) = stale) >= 1JSON form:
{ "dql": "1", "condition": { "type": "cmp", "op": ">=", "left": { "type": "count", "where": { "type": "cmp", "op": "=", "left": { "type": "call", "name": "status", "args": [ { "type": "field", "name": "funding" } ] }, "right": { "type": "status", "value": "stale" } } }, "right": { "type": "number", "value": 1 } } }Request:
{ "query": "count(venues where status(funding) = stale) >= 1", "limit": 1 }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [ { "instrument": "BTC-PERP", "result": "true", "values": {}, "venues": [ { "venue": "BYBIT-PERP", "symbol": "BTCUSDT", "quote": "USDT", "values": { "status(funding)": { "value": "stale", "venue": "BYBIT-PERP", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T11:52:00Z", "age_ms": 485000, "status": "stale" } } }, { "venue": "HYPERLIQUID", "symbol": "BTC", "quote": "USDC", "values": { "status(funding)": { "value": "present", "venue": "HYPERLIQUID", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "KRAKEN-FUTURES", "symbol": "PF_XBTUSD", "quote": "USD", "values": { "status(funding)": { "value": "present", "venue": "KRAKEN-FUTURES", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "OKX-PERP", "symbol": "BTC-USDT-SWAP", "quote": "USDT", "values": { "status(funding)": { "value": "present", "venue": "OKX-PERP", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } } ] } ], "page": { "limit": 1, "returned": 1, "next_cursor": null }}Four situations, one per venue: a reading that arrived a second ago is present; one that arrived eight minutes ago, past the layer’s freshness bound, is stale — the value comes back with its true age, and a comparison treats it as unknown; a layer DEBYKO collects for which nothing has arrived for this listing is missing; a field the venue does not publish at all — Hyperliquid’s mark candles, Kraken’s funding interval — is not_published. A fifth status, off, means DEBYKO does not collect that layer for that instrument’s group by policy. Only the first counts as data in a comparison.
4.11 As of a past moment
Section titled “4.11 As of a past moment”Intent: which instruments had positive funding on three or more venues at 08:00 UTC today, using only what DEBYKO had received by then.
count(venues where funding > 0) >= 3 as of 2026-09-21T08:00:00ZJSON form:
{ "dql": "1", "as_of": "2026-09-21T08:00:00Z", "condition": { "type": "cmp", "op": ">=", "left": { "type": "count", "where": { "type": "cmp", "op": ">", "left": { "type": "field", "name": "funding" }, "right": { "type": "number", "value": 0 } } }, "right": { "type": "number", "value": 3 } } }Request:
{ "query": "count(venues where funding > 0) >= 3 as of 2026-09-21T08:00:00Z", "limit": 1 }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": "2026-09-21T08:00:00Z", "settled": true, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [], "page": { "limit": 1, "returned": 0, "next_cursor": null }}Notice the ages: they are measured against 08:00:00, not against now, and they are up to a minute
because funding history is retained once per minute. settled: true says the answer will not
change if you ask again — the moment is older than the platform’s settle window, so nothing is
still in flight. Every received_at is before 08:00:00; a value received at 08:00:00.100 would not
be visible to this query even if the venue stamped it 07:59:59.
4.12 Relaxed freshness for a historical question
Section titled “4.12 Relaxed freshness for a historical question”Intent: a week ago at noon, did any venue show open interest at all — accepting values up to ten minutes old.
any(venues where oi > 0) freshness 10m as of 2026-09-14T12:00:00ZJSON form:
{ "dql": "1", "freshness": "10m", "as_of": "2026-09-14T12:00:00Z", "condition": { "type": "any", "where": { "type": "cmp", "op": ">", "left": { "type": "field", "name": "oi" }, "right": { "type": "number", "value": 0 } } } }Request:
{ "query": "any(venues where oi > 0) freshness 10m as of 2026-09-14T12:00:00Z", "limit": 1 }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": "2026-09-14T12:00:00Z", "settled": true, "freshness": { "ticker": "10m", "mark": "10m", "oi": "10m", "book": "10m", "stats": "10m", "funding": "10m" }, "instruments": [ { "instrument": "BTC-PERP", "result": "unknown", "values": {}, "venues": [ { "venue": "BYBIT-PERP", "symbol": "BTCUSDT", "quote": "USDT", "values": { "oi": { "value": null, "unit": "base", "venue": "BYBIT-PERP", "layer": "oi", "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" } } }, { "venue": "HYPERLIQUID", "symbol": "BTC", "quote": "USDC", "values": { "oi": { "value": null, "unit": "base", "venue": "HYPERLIQUID", "layer": "oi", "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" } } }, { "venue": "KRAKEN-FUTURES", "symbol": "PF_XBTUSD", "quote": "USD", "values": { "oi": { "value": null, "unit": "base", "venue": "KRAKEN-FUTURES", "layer": "oi", "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" } } }, { "venue": "OKX-PERP", "symbol": "BTC-USDT-SWAP", "quote": "USDT", "values": { "oi": { "value": null, "unit": "base", "venue": "OKX-PERP", "layer": "oi", "venue_ts": null, "received_at": null, "age_ms": null, "status": "missing", "reason": "no_value" } } } ] } ], "page": { "limit": 1, "returned": 1, "next_cursor": "eyJ0IjoiMjAyNi0wOS0xNFQxMjowMDowMFoiLCJhZnRlciI6IkJUQy1QRVJQIiwicSI6Ijg5MDVkMmI1ZmRjNWVhNzEifQ" }}Bybit’s reading above was almost eight minutes old; under the default 30-second bound it would have
been stale, under freshness 10m it is present — and its true age comes back either way. A venue
whose history is kept for a shorter period answers missing with reason not_retained instead:
nothing from that day exists any more. The query still runs; it simply cannot see that venue.
4.13 Like-for-like quotes only
Section titled “4.13 Like-for-like quotes only”Intent: dispersion of mark prices above ten basis points, comparing only listings quoted in a USD stablecoin or USD.
max(venues where quote in (USDT, USDC, USD), mark) / min(venues where quote in (USDT, USDC, USD), mark) - 1 > 10bpsJSON form:
{ "dql": "1", "condition": { "type": "cmp", "op": ">", "left": { "type": "arith", "op": "-", "left": { "type": "arith", "op": "/", "left": { "type": "agg", "fn": "max", "expr": { "type": "field", "name": "mark" }, "where": { "type": "in", "left": { "type": "field", "name": "quote" }, "values": [ { "type": "code", "value": "USDT" }, { "type": "code", "value": "USDC" }, { "type": "code", "value": "USD" } ] } }, "right": { "type": "agg", "fn": "min", "expr": { "type": "field", "name": "mark" }, "where": { "type": "in", "left": { "type": "field", "name": "quote" }, "values": [ { "type": "code", "value": "USDT" }, { "type": "code", "value": "USDC" }, { "type": "code", "value": "USD" } ] } } }, "right": { "type": "number", "value": 1 } }, "right": { "type": "number", "value": 0.001 } } }Request:
{ "query": "max(venues where quote in (USDT, USDC, USD), mark) / min(venues where quote in (USDT, USDC, USD), mark) - 1 > 10bps", "limit": 1 }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [], "page": { "limit": 1, "returned": 0, "next_cursor": null }}DQL never converts currencies. USDT, USDC and USD are three different values of
quote; if you want to treat them as one group, say so in the query, as here. (The comparison page
groups them for display; the language does not.)
4.14 Ordering the screen
Section titled “4.14 Ordering the screen”Intent: instruments answering on at least two venues, highest eight-hour funding first.
venues_answered >= 2 order by max(venues, funding_8h) descJSON form:
{ "dql": "1", "condition": { "type": "cmp", "op": ">=", "left": { "type": "field", "name": "venues_answered" }, "right": { "type": "number", "value": 2 } }, "order_by": { "expr": { "type": "agg", "fn": "max", "expr": { "type": "field", "name": "funding_8h" } }, "direction": "desc" } }Request:
{ "query": "venues_answered >= 2 order by max(venues, funding_8h) desc", "limit": 3 }Response, on the documentation fixture — real venue codes, invented numbers, the query echo dropped:
{ "dql": "1", "evaluated_at": "2026-09-23T12:00:05Z", "as_of": null, "settled": false, "freshness": { "ticker": "30s", "mark": "30s", "oi": "30s", "book": "30s", "stats": "120s", "funding": "300s" }, "instruments": [ { "instrument": "BTC-PERP", "result": "true", "values": { "venues_answered": { "value": 4, "from": "reference", "as_of": "2026-09-23T12:00:05Z" }, "max(venues, funding_8h)": { "value": 0.000104, "unit": "fraction", "from": [ { "venue": "HYPERLIQUID", "value": 0.000104, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } ] } }, "venues": [ { "venue": "BYBIT-PERP", "symbol": "BTCUSDT", "quote": "USDT", "values": { "funding_8h": { "value": 2e-05, "unit": "fraction", "venue": "BYBIT-PERP", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T11:52:00Z", "age_ms": 485000, "status": "stale" } } }, { "venue": "HYPERLIQUID", "symbol": "BTC", "quote": "USDC", "values": { "funding_8h": { "value": 0.000104, "unit": "fraction", "venue": "HYPERLIQUID", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "KRAKEN-FUTURES", "symbol": "PF_XBTUSD", "quote": "USD", "values": { "funding_8h": { "value": 8e-05, "unit": "fraction", "venue": "KRAKEN-FUTURES", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } }, { "venue": "OKX-PERP", "symbol": "BTC-USDT-SWAP", "quote": "USDT", "values": { "funding_8h": { "value": 5.4e-05, "unit": "fraction", "venue": "OKX-PERP", "layer": "funding", "venue_ts": null, "received_at": "2026-09-23T12:00:04Z", "age_ms": 1000, "status": "present" } } } ] } ], "page": { "limit": 1, "returned": 1, "next_cursor": "eyJ0IjoiMjAyNi0wOS0yM1QxMjowMDowNVoiLCJhZnRlciI6IkJUQy1QRVJQIiwicSI6IjE0MTk1NWNjMGNhMzliODEifQ" }}The ordering key appears in values so you can see what the sort used (BTC’s venue list is shortened here; a real response lists all fourteen). Instruments whose key is
null (no venue had a usable funding_8h) sort last, in either direction.
4.15 Alerts, with Agent
Section titled “4.15 Alerts, with Agent”Intent: be told when, for any instrument, the 20/50 EMA cross happens on hourly candles on a venue with a clean 200-candle history.
The condition is a query like any other:
any(venues where crossover(ema(close, 20), ema(close, 50)) and gaps(200) = 0) timeframe 1hThe API does not hold alerts. It answers questions; it does not remember them, does not run them on a schedule and does not deliver anything to a webhook. That belongs to Agent: Agent holds the query, the cadence, the destination and your own tag, calls this API on that cadence, and decides what a firing means.
The reason is not architectural tidiness. A saved rule with a webhook makes the data API responsible for delivery, for retries, and — once a rule can be public — for showing somebody’s words next to a number, which is how a data platform ends up looking like it recommends a trade. The language has no vocabulary for an action or an opinion (spec §7.5), and now it has no tag to store either.
Until Agent opens, the same thing is four lines of your own code: run the query on your cadence,
compare with the previous answer, act on the difference. Everything the query needs — the canonical
form to compare against, items_sha256 to see that nothing changed, settled to know the answer may
still move — is in the response.
4.16 A rate of change on a snapshot field
Section titled “4.16 A rate of change on a snapshot field”Intent: the mark price is more than half a per cent above where it stood five minutes ago, on any
venue. mark is a snapshot field, not a candle, and DQL samples it at the effective timeframe.
any(venues where roc(mark, 5) > 0.5%) timeframe 1mThe samples are the platform’s own per-second readings collapsed to the timeframe (spec §7.2a); a period with nothing retained is a gap, and a gap makes the answer null rather than a smaller number.
4.17 A spread that is unusually wide for this venue
Section titled “4.17 A spread that is unusually wide for this venue”Intent: the current spread sits above the 90th percentile of its own last hour — “wide for this venue”, not “wide compared with a number I guessed”.
any(venues where spread > percentile(spread, 60, 90%)) timeframe 1mpercentile takes the nearest rank by default; add linear as a fourth argument for interpolation
between the two neighbouring readings.
4.18 Real money resting near the mid
Section titled “4.18 Real money resting near the mid”Intent: at least one venue has more than two million quote units resting within ten basis points of the mid on the bid side.
any(venues where depth_bid_value(10bps) > 2000000)The bands are the platform’s, not yours: ask GET /v2/dql/catalogue for the set. A book that did not
reach the band’s edge answers null with reason band_truncated — never the sum of the levels that
happened to arrive.
4.19 Choosing the columns, and ordering by two keys
Section titled “4.19 Choosing the columns, and ordering by two keys”Intent: the instruments where at least two venues answer, the widest funding first and, where that ties, the tighter spread first — and bring back only the three numbers I want.
venues_answered >= 2 select max(venues, funding_8h), min(venues, spread), venues_answered order by max(venues, funding_8h) desc, min(venues, spread) ascselect decides what comes back; without it the answer carries every atom the condition touched.
4.20 The venue’s mark candle, not its trade candle
Section titled “4.20 The venue’s mark candle, not its trade candle”Intent: compare the last closed hourly mark candle with the live mark price.
any(venues where mark > mark_close) timeframe 1hA venue that publishes no mark candles answers not_published for mark_close — Hyperliquid today —
and the comparison is unknown for that venue rather than false.
4.21 Asking for older data on purpose
Section titled “4.21 Asking for older data on purpose”Intent: a question about yesterday, where a reading a few minutes old is perfectly good.
any(venues where oi > 1000) freshness 30m as of 2026-09-20T12:00:00Zfreshness replaces every layer’s default bound at once. It does not make stale data fresh: it
changes where the line between present and stale is drawn for this question, and every value
still comes back with its true age.
4.22 One venue’s own symbol
Section titled “4.22 One venue’s own symbol”Intent: the listing a venue calls BTCUSDT, whatever DEBYKO calls the instrument.
any(venues where mark > 0) where symbol = "BTCUSDT"symbol is the venue’s own string and is quoted; it may appear only in the where selection. A bare
code there (symbol = BTCUSDT) is DQL_TYPE, because a symbol is not a code.
5. Two ways to write the same query
Section titled “5. Two ways to write the same query”Every query has a string form and a JSON form, and they are interchangeable: send either to
POST /v2/screen, and the response contains both, normalised. POST /v2/dql/parse converts and
validates without evaluating — useful for editors and for programs that generate queries.
{ "query": "count(venues where funding_8h > 1bps) >= 3", "context": "screen" }returns the canonical string (1bps becomes 0.0001), the JSON tree, the list of fields and
functions the query touches, and its complexity score — or a precise error with a position and
a hint. Errors are structured:
{ "errors": [ { "code": "DQL_SCOPE", "message": "'funding' is venue-scoped at 1:1", "position": { "line": 1, "column": 1, "offset": 0, "length": 7 }, "hint": "wrap it in any(venues where …), all(…), count(…) or an aggregate" } ] }GET /v2/dql/catalogue lists every field (with type, unit, layer and which venues do not publish
it), every function, every venue, currency and timeframe, the freshness defaults and the limits.
If you generate queries programmatically, read it first.
6. Missing data, for humans
Section titled “6. Missing data, for humans”A DQL comparison can come out three ways: true, false, or unknown. Unknown means “there was no value to compare”, and it happens more often than in a stock screener because DEBYKO refuses to invent values:
- a venue has not sent a field for a while (stale): the last value is shown with its age, but the language will not compare it as if it were current;
- a venue has never sent the field for this instrument, or its history was not kept that long (missing);
- the venue does not have the field at all — an oracle-priced venue has no order book (not_published);
- DEBYKO does not collect that layer for that instrument group (off);
- an indicator needs 80 candles and one is absent (gap), or only 30 exist (insufficient history).
The combining rules are the ones SQL uses for NULL:
unknown and falseis false (one false is enough),unknown and trueis unknown;unknown or trueis true (one true is enough),unknown or falseis unknown;not unknownis unknown;any(...)is true if some venue is true, otherwise unknown if some venue is unknown, otherwise false;all(...)is false if some venue is false, otherwise unknown if some venue is unknown, otherwise true;count(...)counts only venues that are true, so it is never unknown.
A screen returns instruments where the condition is true. A rule fires only on true.
Unknown is silent by default; ask for it with "include_unknown": true when you are debugging a
condition.
Writing conditions that handle it:
| You want | Write |
|---|---|
| ignore venues that do not publish the field | all(venues where status(field) = not_published or <condition>) |
| only count venues with fresh data | count(venues where status(field) = present and <condition>) |
| require a minimum number of answering venues | ... and venues_answered >= 3 |
| treat “no data” as a failure of the screen | leave the condition as it is — unknown is already excluded |
| find the instruments where data quality is the problem | count(venues where status(field) != present) >= 1 |
| accept older readings on purpose | ... freshness 10m |
A useful habit: when a screen returns fewer instruments than you expect, run it once with
include_unknown: true and look at the status and reason of the values. The answer is always
in the response.
The two reasons that are about DEBYKO, not about the venue
Section titled “The two reasons that are about DEBYKO, not about the venue”Seven of the nine reasons say something about the venue or the moment. Two say something about us:
unit_unknown— you asked forunits: baseand we do not know this listing’s contract size, so we will not convert. Ask forunits: publishedand do the conversion yourself, or tell us the contract rules are missing.unmapped— the listing is collected but belongs to no market yet, so it has noinstrument. It still answers byvenueandsymbol; it just cannot be compared with anything.
6a. Four mistakes, and what the API says about them
Section titled “6a. Four mistakes, and what the API says about them”Each of these is rejected before anything is evaluated, with the position and a suggestion. They are worth reading once: three of the four are the difference between DQL and the query languages it resembles.
A venue-scoped field on its own. Which venue’s funding? The language will not guess.
funding > 0
'funding' is venue-scoped; wrap it in any(venues where …), all(…), count(…) or an aggregate
A series without a period. ema over what candles?
any(venues where ema(close, 20) > 0)add
timeframe 1h, or write the period into the call:ema(close, 20, 1h)
A period the platform does not keep.
any(venues where ema(close, 20) > 0) timeframe 7m
timeframe '7m' is not supported; supported: 1m, 2m, 3m, 5m, 15m, 30m, 1h, 4h, 12h, 1d
A venue’s symbol written as a code. symbol is the venue’s own string, so it is quoted.
any(venues where mark > 0) where symbol = BTCUSDT
symbol takes a quoted string
7. Coming from Pine Script
Section titled “7. Coming from Pine Script”DQL borrows indicator names, nothing else. Pine is a scripting language that runs bar by bar over a chart; DQL is a single condition evaluated over many instruments and venues at one instant.
| In Pine you write | In DQL you write | Note |
|---|---|---|
ta.ema(close, 20) |
ema(close, 20) |
no ta. prefix; fixed 80-candle window with SMA seed (see spec §7.3) |
close |
close |
the last completed candle; the forming candle is never used |
close[1] |
prev(close, 1) |
no [] operator |
ta.crossover(a, b) |
crossover(a, b) |
same meaning: a > b now and a <= b one candle earlier |
ta.rsi(close, 14) |
rsi(close, 14) |
Wilder smoothing, fixed 56-change window |
ta.atr(14) |
atr(14) |
same |
| chart timeframe | timeframe 15m clause, or ema(close, 20, 15m) |
no implicit timeframe |
request.security(...) |
trailing timeframe argument per function | several timeframes in one condition |
and or not |
and or not |
same words; not binds below comparisons (not a > b is not (a > b)) |
== != |
= != (== and <> accepted) |
|
na(x) |
status(x) != present |
five statuses instead of one na |
nz(x, 0) |
— | deliberately absent; missing is never zero |
ta.percentile_nearest_rank(S, n, p) |
percentile(S, n, p) |
and percentile(S, n, p, linear) for interpolation |
math.abs(x) |
abs(x) |
element-wise over a series |
math.sum(S, n) |
rolling_sum(S, n) |
|
| a script per symbol, switched by hand | where base = BTC, where venue = KRAKEN-FUTURES, where symbol = "BTCUSDT" |
selection is part of the query |
input, var, :=, if, for, plot, strategy.*, alertcondition |
— | not a scripting language |
| one symbol per script | any(venues where ...), all(...), count(...), max(venues, ...) |
every venue value needs a venue scope |
Numbers will not match TradingView exactly. TradingView’s EMA depends on how much chart history is loaded; DQL’s uses a fixed window so that anyone can recompute it from public candles.
8. Coming from SQL
Section titled “8. Coming from SQL”Think of a table with one row per (instrument, venue). A DQL condition is a WHERE clause over
that table, grouped by instrument.
| In SQL you write | In DQL you write | Note |
|---|---|---|
WHERE funding > 0 |
any(venues where funding > 0) |
the group-by is built in; say which rows |
HAVING COUNT(*) FILTER (WHERE funding > 0) >= 3 |
count(venues where funding > 0) >= 3 |
|
HAVING MAX(mark) - MIN(mark) > 5 |
max(venues, mark) - min(venues, mark) > 5 |
|
MAX(mark) FILTER (WHERE quote = 'USDT') |
max(venues where quote = USDT, mark) |
codes are unquoted, uppercase |
venue IN ('KRAKEN-FUTURES', 'BYBIT-PERP') |
venue in (KRAKEN-FUTURES, BYBIT-PERP) |
|
funding IS NULL |
status(funding) != present |
and the status tells you why |
NULL semantics in AND/OR/NOT |
identical | three-valued logic |
AVG(mark) |
— | deliberately absent: venues are never averaged |
SUM(oi) |
sum(venues, oi) |
only additive quantities can be summed |
AS OF SYSTEM TIME '...' |
as of 2026-09-21T08:00:00Z |
visibility by received_at, not by venue timestamp |
ORDER BY x DESC LIMIT 50 |
order by x desc in the query, "limit": 50 in the request |
|
WHERE base = 'BTC' (choosing rows before aggregating) |
the where clause: … where base = BTC |
selection and condition are separate; the clause runs first |
SELECT a, b, c |
select max(venues, funding_8h), min(venues, spread) |
chooses what comes back, not what is compared |
ORDER BY a DESC, b ASC |
order by max(venues, funding_8h) desc, min(venues, spread) asc |
several keys, C1-06 |
WHERE symbol = 'BTCUSDT' |
where symbol = "BTCUSDT" |
the venue’s own symbol is a quoted string, not a code |
SUM(oi) across venues with mixed contract sizes |
sum(venues, oi) with units: base |
refused under units: published, because contracts do not add up |
LIKE, joins, subqueries, CASE |
— | not in the language |
Keywords are lowercase (and, not AND); uppercase words are venue, currency and asset codes.
9. What DQL deliberately does not do
Section titled “9. What DQL deliberately does not do”- It does not express actions or opinions. There is no
buy,sell,long,short,signal,entry,exit,strengthorconfidenceanywhere in the language, and there will not be.ema(close, 20) > ema(close, 50)is a statement about two numbers; what you do with it is not DEBYKO’s business. The alert tag is text you choose; DEBYKO stores it, sends it back, and never reads it. This keeps DEBYKO a data service, and it keeps your rules yours. - It does not blend venues. No average price, no consensus price, no “best” price. You can ask for the maximum, the minimum, the count, the sum, or any single venue.
- It does not fill gaps. A missing candle stays missing and an indicator over it is null. If
you want indicators that tolerate holes, you have all the candles via
/v1/candlesand can compute your own. - It does not convert currencies. A price is a number in the venue’s quote currency.
- It does not run code. No variables, loops, user-defined functions or state. A query is one condition. If you need more, the data endpoints are there.
- It does not let you see the future.
as ofuses only what had been received by that moment, including candles that had not yet arrived.
Nothing in a response is a forecast, a rating or advice. Every number is something a venue published, or an arithmetic function of such numbers defined in the specification, with the venue, the receipt time and the age attached.
10. Every function, with one example
Section titled “10. Every function, with one example”One runnable query per function, the function’s name visible in the query itself. Every line here is
run by GuideExampleTests against the conformance fixture, so an example that stops working fails
the build instead of sitting here misleading you.
Series functions — all need a period, from the timeframe clause or a trailing argument:
any(venues where close > prev(close, 1)) timeframe 15many(venues where close > sma(close, 20)) timeframe 15many(venues where ema(close, 20) > ema(close, 50)) timeframe 1hany(venues where rsi(close, 14) < 30) timeframe 1hany(venues where atr(14) > 100) timeframe 15many(venues where stdev(close, 20) > 50) timeframe 15many(venues where close >= highest(close, 50)) timeframe 15many(venues where close <= lowest(close, 50)) timeframe 15many(venues where change(close, 10) > 0) timeframe 15many(venues where roc(close, 12) > 1%) timeframe 5many(venues where crossover(close, sma(close, 3))) timeframe 15many(venues where crossunder(sma(close, 3), close)) timeframe 15many(venues where gaps(100) = 0) timeframe 15many(venues where abs(change(close, 5)) > 100) timeframe 15many(venues where rolling_sum(volume, 12) > 1000) timeframe 5many(venues where close > percentile(close, 100, 95%)) timeframe 15many(venues where close > percentile(close, 100, 95%, linear)) timeframe 15many(venues where percentile_rank(close, 100) > 0.8) timeframe 15mData quality — these two are the reason the language exists:
any(venues where age(mark) < 5s)any(venues where status(funding) = stale)Book depth — the bands are the platform’s, from GET /v2/dql/catalogue:
any(venues where depth_bid(10bps) > 5)any(venues where depth_ask(10bps) > 5)any(venues where depth_bid_value(10bps) > 250000)any(venues where depth_ask_value(10bps) > 250000)11. Every field, with one example
Section titled “11. Every field, with one example”The instrument, and the two counts that say how many venues answered:
venues_answered >= 2 where instrument = BTC-PERPvenues_answered >= 2 where base = BTCvenues_listed >= 3venues_answered < venues_listedThe listing — what a venue calls it and what its contract rules are:
any(venues where venue = KRAKEN-FUTURES and mark > 0)any(venues where quote = USDT and mark > 0)venues_answered >= 1 where symbol = "BTCUSDT"any(venues where tick_size <= 0.1)any(venues where lot_size <= 0.001)any(venues where min_qty <= 0.001)What the venue publishes, layer by layer:
any(venues where last > 0)max(venues, mark) - min(venues, mark) > 5bpsany(venues where index > 0)any(venues where basis > 10bps)any(venues where funding > 0)any(venues where funding_interval = 8h)any(venues where funding_8h > 1bps)any(venues where time_to_funding < 30m)sum(venues, oi) > 10000any(venues where ask - bid > 0)any(venues where mid > 0)all(venues where status(bid) = not_published or spread < 5bps)any(venues where bid_size > 1 and ask_size > 1)any(venues where volume_24h > 10000)any(venues where turnover_24h > 100000000)any(venues where change_24h < -1%)Candles. Trade candles are open high low close volume; the venue’s mark and index candles carry the
same five names with a prefix. A venue that publishes no mark candle answers not_published for every
mark_* source rather than repeating its trade candle:
any(venues where high - low > 0 and close > open) timeframe 1hany(venues where volume > 0) timeframe 1hany(venues where mark_high - mark_low > 0 and mark_close > mark_open) timeframe 1hany(venues where mark_volume >= 0) timeframe 1hany(venues where index_high - index_low > 0 and index_close > index_open) timeframe 1hany(venues where index_volume >= 0) timeframe 1h