---
title: Feature Definition Query
description: Query native feature definitions by indicator, event or state kind, and feature ID for AI agents, chart components and API consumers.
---

# Feature Definition Query

`GET /api/indicators/signals` returns feature definitions grouped by indicator: available features,
judgment rules, meanings and validity requirements. It does not return current observations for a
symbol or trading recommendations.

Base URL: `https://api.mobiusquant.ai`. The usual [optional Bearer token and API rate limits](/token)
apply. An administrator account is not required.

## Requests

```bash
# All indicators, including those without independent features and their reasons
curl 'https://api.mobiusquant.ai/api/indicators/signals'

# All MACD features
curl 'https://api.mobiusquant.ai/api/indicators/signals?indicator=macd'

# MACD events, excluding ongoing states
curl 'https://api.mobiusquant.ai/api/indicators/signals?indicator=macd&kind=event'

# One feature definition
curl 'https://api.mobiusquant.ai/api/indicators/signals?id=macd.cross_up'
```

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `indicator` | string | No | Exact indicator name, as in `calc[].name`, e.g. `macd`; omit for all indicators |
| `kind` | `event` / `state` | No | Filter by events or states; omit for both |
| `id` | string | No | Full feature ID, e.g. `macd.cross_up` |

Names and IDs are case-sensitive, with no fuzzy matching. Filters combine with **AND**.
When both `indicator` and `id` are supplied, the ID must belong to that indicator.

- Without `kind` or `id`, indicators without features remain present with a reason and `signals: []`.
- With `kind` or `id`, only groups containing matching features are returned.
- A valid query with no matches returns `200` with `{"count":0,"signal_count":0,"indicators":{}}`,
  for example `indicator=cvd&kind=state`.
- An empty string is not an omitted parameter; empty values return `422`.

## Response

For `?id=macd.cross_up`:

```json
{
  "count": 1,
  "signal_count": 1,
  "indicators": {
    "macd": {
      "has_signals": true,
      "signal_status": "supported",
      "signal_reason": "",
      "signals": [
        {
          "id": "macd.cross_up",
          "label": "MACD 金叉",
          "kind": "event",
          "description": "前根 MACD <= Signal，本根 MACD > Signal。",
          "meaning": "MACD 相对信号线或零轴的动量关系；事件只在交叉根出现，状态可持续，不是买卖指令。",
          "required_history": "max(fast, slow) + signal 根连续有效输入",
          "version": 1,
          "details": [],
          "supports_forming": true
        }
      ]
    }
  }
}
```

### Group fields

| Field | Meaning |
| --- | --- |
| `count` | Number of indicator groups returned after filtering |
| `signal_count` | Number of feature definitions returned after filtering |
| `indicators` | Object keyed by indicator name; read by key, not position |
| `has_signals` | Whether the indicator provides feature output, not whether current data is ready |
| `signal_status` | `supported`: features available; `none`: no independent features; `unreviewed`: no feature decision yet |
| `signal_reason` | Reason for no feature output; empty string for supported indicators |
| `signals` | Matching definitions, not calculated values; empty array when no features exist |

Do not equate `none` with `unreviewed`, or interpret an empty type-filtered result as evidence
that an indicator has no features of any kind.

### Feature fields

| Field | Type | Meaning |
| --- | --- | --- |
| `id` | string | Stable feature ID; matches calculated column names and chart marker `signal_ids` |
| `label` | string | Display name |
| `kind` | string | `event`: occurs on one bar; `state`: evaluated per bar and may persist |
| `description` | string | Judgment rule using the actual calculation request's parameters |
| `meaning` | string | Interpretation of the feature |
| `required_history` | string | History and validity requirements; may contain parameter formulas, not a fixed integer |
| `version` | integer | Version of this feature definition |
| `details` | string[] | Additional boundaries or confirmation requirements; empty when none |
| `supports_forming` | boolean | Supports `include_forming: true` final-sample trials; not a data-readiness guarantee |

Names and explanatory text are currently Chinese; IDs and enums are language-independent.
Get calculation parameters, defaults and indicator interval constraints from
[`GET /api/indicators/registry`](https://api.mobiusquant.ai/api/indicators/registry).
`GET /api/indicators/registry?include_signals=true` remains available for compact definitions.

## Using definitions with calculation and charts

1. Query this endpoint to select features by `indicator` and `id`.
2. Send a calculation request to `/api/indicators` or `/api/indicators/compute` with top-level `"signals": true`.
3. Locate the definition's `id` in `indicators[response_key].signals.columns` and read that column in `data`.

For charts, send `"signals": true` to `/api/chart`. Feature tables are in `indicator_signals[response_key]`.
Event markers expose `signal_ids` that join to this directory for names, rules and meanings.
States do not generate per-bar event markers. When using multiple parameter sets for the same indicator,
identify instances by response key / `indicator_key`; feature IDs do not change with parameters.
The directory's query parameter `id` is a feature ID, not the caller-defined calculation `calc[].id`.

Definition availability does not imply that data can be evaluated:

| Calculated value | Meaning |
| --- | --- |
| `true` | Event occurred or state holds on a closed candle |
| `false` | Valid evaluation, but event did not occur or state does not hold |
| `null` | Unknown: forming candle, insufficient history, invalid dependencies or input gaps |

Live features support the connected Binance, Bybit, OKX and Hyperliquid feeds, including
[Binance stock perpetuals](/markets#binance-stock-perp), but not stock spot venues. BYO requires an explicit `interval` and continuous,
fixed-duration candles, subject to each selected indicator's constraints. Set `last_bar_closed: false`
for a forming last candle. After an interval gap or invalid OHLCV, subsequent features in that input are `null`.
Feature `open_time` and `confirmed_at_ms` use milliseconds; chart marker `time` uses seconds.
A confirmation time does not imply that an event occurred. Indicators without feature capability return
the entire `signals: null` in calculation results, unlike the directory's `signals: []`.

See [native feature calculation](/compute#native-features) and [feature charting](/compute#feature-chart) for examples.

## Forming-candle trials {#forming}

Set top-level `signals: true` and `include_forming: true` on `/api/indicators`, `/api/indicators/compute`
or `/api/chart`. `include_forming` defaults to `false` and accepts only a JSON boolean. Using it without
`signals: true` returns `422`.

```bash
curl -X POST 'https://api.mobiusquant.ai/api/indicators?explain=false' \
  -H 'Content-Type: application/json' \
  -d '{"exchange":"binance","market":"perp","symbol":"BTCUSDT","interval":"5m","limit":2,"signals":true,"include_forming":true,"calc":[{"name":"macd","id":"m"}]}'
```

The engine treats the **current actual OHLCV of the final forming candle as a complete sample** for this
calculation. It does not predict the final close or invent future candles. History, continuity, native thresholds,
structural windows and model confirmation requirements are unchanged. Crossings compare adjacent candles,
not consecutive requests. Repeating a request does not increase confirmation counts or deduplicate features.

There is one result: `indicators[key].signals` for calculation and `indicator_signals[key]` for charting.
The table appends `bar_status` after the existing feature columns; always read columns by name:

| bar_status | Meaning | confirmed_at_ms |
| --- | --- | --- |
| `closed` | Closed-bar calculation | Existing interval-based close timestamp |
| `forming` | Current snapshot trial of the final forming bar | Always `null` |

`true` means the condition currently holds and can support an intrabar warning; `false` means it does not hold;
`null` still means dependencies are insufficient or invalid. **A trial may disappear on the next update or at
close. It is not a confirmed event.** Values, features, reasons and native objects come from the same calculation.
For example, `confirmed` in a CVD feature ID is not proof of closure: read `bar_status` and the object's `status`.
Trial CVD results use `objects.schema_version=2`, with `status=forming` and `confirmed_at_ms=null` for provisional divergences.

BYO callers must still supply `last_bar_closed: false` and `interval`. Historical intrabar snapshots are allowed;
all preceding bars must be closed. Fully closed input returns only `closed` rows. Live latest requests without
the current forming candle return `503` with `Retry-After`. Explicit historical windows never relabel a previous
candle as forming. The option does not provide intrabar snapshot replay or an additional source-finality guarantee;
`calculation.as_of_ms` is not the upstream observation time.

Chart event markers carry the same `bar_status`, with null confirmation times for trials; no new color rules or
special drawing primitives are added. Existing `include` options still work. Trial `calculation` metadata adds
`include_forming=true` and `signal_evaluation_version=1`. Data-free definitions expose
`limits.live_features.supports_forming`, consistent with the feature directory. Unsupported forming features
are null, with `forming_not_supported` when reasons are requested. Indicators without native features still return `signals: null`.

Omitting or disabling `include_forming` preserves the original numeric/object/feature/chart behavior and omits
the `bar_status` column. Alert storage, intrabar-to-close correlation, notifications, deduplication and cooldown remain consumer responsibilities.

## Optional calculation information {#single-calculation}

Calculation and chart requests accept an optional top-level `include` array:

```json
{
  "exchange": "binance",
  "market": "perp",
  "symbol": "BTCUSDT",
  "interval": "5m",
  "limit": 100,
  "calc": [{"name": "macd"}, {"name": "cvd"}, {"name": "hmm_regime"}],
  "signals": true,
  "include": ["signal_reasons", "signal_details", "calculation", "definition"]
}
```

Use this body with `/api/indicators?explain=false` or `/api/chart`. BYO accepts the same options.
Omitting `include`, or passing `[]`, preserves the existing response. Unknown or duplicate options
return `422`. `signal_reasons` and `signal_details` require `signals: true`;
`calculation` and `definition` work independently. Indicators without features still return `signals: null`.

| Option | Calculation response | Chart response |
| --- | --- | --- |
| `signal_reasons` | `indicators[key].signals.invalid_reasons` | `indicator_signals[key].invalid_reasons` |
| `signal_details` | `indicators[key].signals.details` | `indicator_signals[key].details` |
| `calculation` | `indicators[key].calculation` | `indicator_calculations[key]` |
| `definition` | `indicators[key].definition` | `indicator_definitions[key]` |

### Reasons for unknown features

`invalid_reasons` uses `{columns, data}`. The first column is `open_time` in milliseconds;
the remaining columns are feature IDs. Only rows containing at least one unknown feature are included.
Unknown cells contain arrays of reason codes; valid cells contain `null`, meaning no invalidity reason.
Read the actual three-state result from `signals.data`, not from this explanation table.
When all features are valid, the explanation table has an empty `data` array.

| Code | Meaning |
| --- | --- |
| `bar_not_closed` | A required bar has not closed |
| `insufficient_history` | More history or a previous valid observation is required |
| `input_gap` | A gap in this calculation's input affects the observation |
| `invalid_input` | Invalid OHLCV in this input affects the observation |
| `invalid_dependency` | Required values, volume conditions or native objects are unavailable |
| `state_unavailable` | No required valid direction or state exists |
| `not_applicable` | A native prerequisite is not met, such as testing support restoration across different HMM directions |

Reasons are per feature and per bar. Multiple reasons may apply, including problems with the
previous bar for crossing events. Existing booleans and nulls do not change. BYO NaN/inf inputs
are still rejected; enabling explanations does not relax input validation.

### Native value and object links

`details.bindings` is keyed by feature ID:

- `values[]`: a `field` and `offset` (`0` for the feature bar, `-1` for the previous input bar).
- `objects`: `collection`, `match`, `id_field`, `fields` and response `container`.
  Charts also supply the actual `object_key`; do not assume it equals the indicator name.
- `context`: `{columns, data}`, supplying only necessary native value rows not otherwise available
  in this response. Timestamps remain milliseconds.

Explicit bindings currently cover MACD, RSI, CVD and HMM. Other indicators return empty bindings
and context when no property links are declared; this does not change their feature support.

CVD matches the same instance's `objects.divergences` by
`confirmation_bar_time_ms = feature open_time` and direction. Read the existing object ID, strength,
endpoints and confirmation time. Charts use `indicator_objects[object_key].divergences`.
Strength is not duplicated. An object ID is a result link, not a notification deduplication guarantee.

HMM transitions link previous/current `hmm_state` or `hmm_context_state`; support events also link
`hmm_supported`. Raw responses reuse returned rows and add a clipped previous row to `context` when
needed. Charts lack a complete numeric table, so context supplies fields needed for true events/states.
These values come from the same calculation, not a second request.

### Calculation metadata

Each indicator instance returns:

- `algorithm_version`: indicator algorithm version, not deployment version.
- `effective_params`: supplied parameters plus applied defaults.
- `resolved`: effective automatic choices, such as HMM normalization `d1` or `legacy`.
- `signal_versions`: feature ID to definition version; empty for indicators without features.
- `input` and `output`: actual calculation history and returned window, each with
  `start_open_time_ms`, `end_open_time_ms` and `bars`. Endpoints are bar open times in milliseconds;
  empty ranges have null endpoints and zero bars.
- `interval_ms`, `as_of_ms`, `last_bar_closed`: input duration, live evaluation cutoff and input
  last-bar closure. BYO normally has a null cutoff and uses the caller's closure assertion.

An input range does not guarantee continuity, sufficient warmup or immutable market data.
Parameters, versions and ranges alone cannot guarantee reproducibility after historical revisions.
Version storage, change detection and migration belong to the consumer.

## Structured indicator definitions {#indicator-definition}

```http
GET /api/indicators/registry?include_schema=true
```

Each indicator gains a `definition` with parameters, output fields, restrictions, algorithm/feature
versions, effective defaults, resolved choices and native value bindings. No candles are read.
For parameter-dependent fields, use:

```http
POST /api/indicators/registry/resolve
Content-Type: application/json
```

```json
{
  "interval": "4h",
  "calc": [
    {"name": "ema", "params": {"period": 50}, "id": "ema50"},
    {"name": "hmm_regime", "id": "hmm"}
  ]
}
```

The response is `{count, indicators}`, keyed by calculation instance keys. EMA resolves to `ema_50`;
HMM resolves to Legacy normalization. No candles are required. `interval` is optional; omitting it
leaves interval-dependent choices unresolved. `calc` supports up to 100 entries.

- `parameters`: declared type, default, required/nullable flags and explicit `constraints`.
  `validation=native` preserves original indicator validation.
- `parameter_constraints_complete=false`: constraints are not exhaustive. Empty constraints do not
  mean arbitrary values are accepted. Successful resolution does not guarantee arbitrary candle
  inputs can be calculated successfully.
- `outputs[]`: resolved `name/type/nullable/unit`, optional bounds, descriptions and enum meanings.
  Do not hard-code default parameter-dependent column names.
- Units: `price` follows input prices, `input_volume` follows input volume, `unix_ms` is milliseconds,
  `category` is an enum, and `model_weight` is not a calibrated probability.
- `limits`: intervals, live intervals, asset classes, volume requirements, unsupported venues and
  live-feature markets. Null interval/asset lists add no indicator-specific restriction; still check
  actual venue availability through `/api/markets` and `/api/intervals`.
- `signal_value_bindings`: data-free native links. Actual chart object keys are in the current
  response's `signals.details`.

These options do not define monitoring rules, polling progress, notifications or trade recommendations.

## Errors

| HTTP status | Cause | Action |
| --- | --- | --- |
| `400` | Unknown indicator/feature ID, or indicator and ID ownership mismatch | Read `detail` and correct the query |
| `422` | Invalid `kind`, empty parameter or excessive length | Maximum lengths: 128 for `indicator`, 200 for `id`; use documented enum values |
| `401` | Invalid, expired or revoked supplied token | Correct the token; do not silently retry anonymously |
| `429` | API rate limit exceeded | Respect `Retry-After`; do not reload definitions for every candle |
| `405` | POST, PUT, PATCH or DELETE | Use GET; this endpoint is read-only |

Field types and query parameters are also available through [OpenAPI](https://api.mobiusquant.ai/openapi.json).
