Saltar a contenido

Catálogo y ayuda

Qué vas a aprender

  • La diferencia entre plantilla (receta del catálogo) e instancia (plantilla + parámetros concretos).
  • Cómo listar las 31 plantillas de estrategia con GET /v1/catalog/strategies y ver la ficha de una.
  • Cómo explorar el catálogo completo de indicadores (386 entradas) con GET /v1/indicators y sus filtros.
  • Qué significan los niveles de capacidad discovered → implemented → verified → approved, y por qué la API calcula 375 indicadores pero solo 33 con garantías canónicas.
  • Cómo pedir la ficha de un indicador con GET /v1/indicators/{name}, incluidos nombres nativos de cada motor.
  • Cómo usar la superficie de ayuda (GET /v1/help y GET /v1/help/{name}) como manual de bolsillo.

Todas las rutas de este capítulo son GET de solo lectura: no ejecutan nada ni escriben en disco. Son la «carta del restaurante» antes de pedir.


1. El mapa: tres catálogos, una ayuda

flowchart TB
    subgraph Estrategias
      CS["GET /v1/catalog/strategies<br/>31 plantillas P01..P20, Q01..Q10"] --> CT["GET /v1/catalog/strategies/{template_id}<br/>ficha completa"]
    end
    subgraph Indicadores
      I["GET /v1/indicators<br/>386 entradas (filtros)"] --> ID["GET /v1/indicators/{name}<br/>ficha de uno"]
    end
    subgraph Ayuda
      H["GET /v1/help<br/>417 temas"] --> HN["GET /v1/help/{name}<br/>dossier de uso"]
    end
    CT -. mismo id .-> HN
    ID -. mismo nombre .-> HN
Pregunta que te haces Ruta
¿Qué estrategias hay? GET /v1/catalog/strategies
¿Qué parámetros acepta P01 y qué restricciones tiene? GET /v1/catalog/strategies/P01 o GET /v1/help/P01
¿Qué indicadores existen en VectorTA y en Nautilus? GET /v1/indicators?engine=…
¿Puedo calcular este indicador por la API? GET /v1/indicators/{name}capability.computable_via_api
¿Cómo lo llamo en Python o con curl? GET /v1/help/{name}examples, example_curl

2. Plantillas de estrategia

2.1 Plantilla frente a instancia

Una plantilla es como una receta de cocina («tortilla de patatas: huevos, patatas, cebolla opcional»). Una instancia es la tortilla concreta que haces hoy («6 huevos, 500 g de patata, con cebolla») y a la que das nombre.

Nivel Qué es Quién la define Ejemplo
Plantilla Una de las 31 recetas del catálogo: regla canónica, defaults, parameter_grid, esquema de params, features, formula_version El proyecto (catalog/strategy_catalog.json) P01 «Cruce de medias EMA»
Instancia Plantilla + params + nombre + id determinista P01-dc9260 «EMA cross 20/50 base»

GET /v1/catalog/strategies lista plantillas, no instancias. Para instancias está GET /v1/strategies (lo verás en el capítulo 3). El detalle de cada plantilla está explicado en la Parte V.

2.2 GET /v1/catalog/strategies

Filtros de consulta (según finazbench/api/routers/catalog.py):

Parámetro Valores Nota
category popular | quant Populares P01–P20; cuantitativas Q01–Q10.
milestone H1..H5 Hito del plan en que entra la plantilla.
status p. ej. implemented Estado de implementación.
supported_by adaptador Hoy responde 501 ENDPOINT_NOT_IMPLEMENTED (el código 37 del servidor, fuera de los 36 del contrato; ver capítulo 1): filtrar por adaptador exigiría la matriz real probada, y el servidor prefiere negarse a devolver una lista basada en una conjetura.
BASE=http://localhost:8000
curl -sS "$BASE/v1/catalog/strategies" | jq '.payload | {catalog_version, count}'
curl -sS "$BASE/v1/catalog/strategies?category=popular&milestone=H1" \
  | jq -r '.payload.templates[] | [.template_id, .name] | @tsv'
import requests
BASE = "http://localhost:8000"
p = requests.get(f"{BASE}/v1/catalog/strategies",
                 params={"category": "popular"}).json()["payload"]
for t in p["templates"]:
    print(t["template_id"], t["name"], t.get("defaults"))

Extracto de la forma de la respuesta según el contrato (docs/API.md §6.2):

{
  "payload": {
    "catalog_version": "1.0.0",
    "count": 31,
    "templates": [
      { "template_id": "P01", "name": "Cruce de medias EMA", "category": "popular",
        "formula_version": "1.0.0", "milestone": "H1", "status": "implemented",
        "features": ["ema_fast", "ema_slow"], "defaults": { "fast": 20, "slow": 50 },
        "grid_size": 16, "valid_grid_size": 16, "data_requirements": ["OHLC"],
        "expected_algorithmic_cost": "O(N)" },
      "…"
    ]
  }
}

grid_size frente a valid_grid_size

grid_size es el producto cartesiano de la rejilla; valid_grid_size descuenta las combinaciones que violan restricciones. En P01 (fast ∈ {5,10,20,40}, slow ∈ {50,100,150,200}) coinciden: 16 y 16, porque el mayor fast (40) es menor que el menor slow (50). En P11, de 15 parejas, entry=20, exit=20 viola exit < entry, así que valid_grid_size es 14.

¿Por qué Q08 y Q08B son dos plantillas?

El catálogo original tiene un único Q08 con model como parámetro. La API lo publica partido: Q08 (Ridge, siempre disponible) y Q08B (CatBoost, dependencia opcional). El soporte es una propiedad de la plantilla, no de un parámetro: si fuera un parámetro, /v1/capabilities no podría declarar UNSUPPORTED para una parte del espacio de parámetros.

2.3 GET /v1/catalog/strategies/{template_id}

La ficha completa: regla canónica, param_schema (JSON Schema), constraints, parameter_grid, y sobre todo features_detail con depends_on_params, que es lo que permite a la API saber qué se reutiliza al cambiar un parámetro.

curl -sS "$BASE/v1/catalog/strategies/P01" \
  | jq '.payload | {canonical_rule, constraints, features_detail: [.features_detail[] | {name, depends_on_params}]}'

Forma según el contrato:

{
  "canonical_rule": "Con ambas medias válidas, objetivo +1 si EMA(f)>EMA(s), -1 si EMA(f)<EMA(s); igualdad conserva el objetivo anterior. …",
  "constraints": [
    { "rule": "fast < slow", "message": "slow debe ser estrictamente mayor que fast.", "fields": ["fast", "slow"] }
  ],
  "features_detail": [
    { "name": "ema_fast", "depends_on_params": ["fast"] },
    { "name": "ema_slow", "depends_on_params": ["slow"] }
  ]
}

Una plantilla inexistente devuelve 404 TEMPLATE_NOT_FOUND.

Lectura de trader

Si ema_slow depende solo de slow, subir slow de 50 a 100 no obliga a recalcular la EMA rápida. Es la base del ahorro en barridos y derivaciones.


3. El catálogo completo de indicadores

3.1 386 descubiertos no son 386 calculables

GET /v1/indicators enumera todo lo que exponen los motores instalados, fusionado con las primitivas registradas del proyecto. Los números del ejemplo real examples/api/08_indicators_catalog.response.json:

Medida Valor
Tamaño del catálogo (catalog_size) 386
Con carril VectorTA 346
Con carril Nautilus 36
En los dos motores (both_engines) 15
Primitivas registradas del proyecto 39
Calculables por POST /v1/indicators (computable_via_api) 375
…con fórmula canónica y paridad verificada (execution_mode: canonical) 33
…por paso directo al motor, sin garantías (capability_counts.computable_passthrough) 342
No ejecutables, con not_computable_reason 11

¿Por qué solo 33 con garantías? Porque «existir en una librería» (o poder calcularse por paso directo) no es lo mismo que «tener un contrato». Cada entrada publica su nivel de capacidad, una escalera monótona:

stateDiagram-v2
    direction LR
    [*] --> discovered: lo expone un motor
    discovered --> implemented: fórmula canónica, params,<br/>first_valid, warmup, clave de caché
    implemented --> verified: test de fidelidad<br/>+ test de paridad entre carriles
    verified --> approved: registro de aprobación<br/>scope benchmark/research
Nivel Significado ¿Calculable por la API?
discovered Un motor lo expone, pero el proyecto no tiene contrato. Lleva promotion_note. Solo por paso directo al motor (342 de 347), sin garantías; el nivel no sube
implemented Primitiva registrada con fórmula canónica, params, first_valid, warmup y rol de caché. Sí, canónico (33 de 39)
verified + evidencia automatizada (tests de fidelidad y paridad) en capability.evidence.
approved + registro en finazbench.features.approval, alcance benchmark y research, production: false.

Del ejemplo real: capability_counts = {"discovered": 347, "implemented": 39, "verified": 39, "approved": 39, "computable_passthrough": 342} y computable_via_api = 375. Los niveles son acumulativos, así que discovered + implemented = 347 + 39 = 386 = catalog_size; computable_passthrough no es un nivel, solo cuenta las entradas discovered que el paso directo sabe calcular.

production: false siempre

Este repositorio es un banco de pruebas. Ninguna aprobación es para producción, y la API lo declara en cada ficha. No leas approved como «apto para operar con dinero real».

3.2 Filtros de GET /v1/indicators

Parámetro Valores Defecto Efecto
engine all | vectorta | nautilus all Solo entradas con carril en ese motor.
registered all | true | false all true = las 39 primitivas; false = solo de motor.
verified all | true | false all true = verified y approved.
approved all | true | false all true = las del registro de aprobación (39).
mode all | canonical | passthrough all canonical = las 39 primitivas; passthrough = las 347 discovered (342 ejecutables por paso directo).
view summary | full summary full añade módulo/función/clase de las entradas de motor, passthrough_params y, si es calculable, example_request y example_curl.
q subcadena (≤200) Búsqueda sin mayúsculas en nombre y descripción.
limit / offset 1..2000 / ≥0 500 / 0 Paginación.

Tres campos de conteo que conviene no confundir:

  • count: entradas en esta respuesta.
  • total: entradas que pasan los filtros, antes de limit/offset.
  • catalog_size: tamaño sin filtrar (386).

Resultados reales de los ejemplos regenerados:

Petición Fichero count total
GET /v1/indicators 08_indicators_catalog.response.json 386 386
GET /v1/indicators?engine=nautilus&limit=40 11_indicators_catalog_nautilus.response.json 36 36
GET /v1/indicators?approved=true 16_indicators_catalog_approved.response.json 39 39
# Tamaño e inventario
curl -sS "$BASE/v1/indicators" | jq '.payload | {count, total, catalog_size, capability_counts, computable_via_api}'

# Los 36 nativos de Nautilus
curl -sS "$BASE/v1/indicators?engine=nautilus" | jq '.payload.total'

# Las 39 primitivas aprobadas
curl -sS "$BASE/v1/indicators?approved=true" | jq -r '.payload.indicators[].name'

# Buscar por texto
curl -sS "$BASE/v1/indicators?q=bollinger" | jq -r '.payload.indicators[] | [.name, .both_engines] | @tsv'

# Paginar las de solo-motor
curl -sS "$BASE/v1/indicators?registered=false&limit=10&offset=10" | jq -r '.payload.indicators[].name'
import requests
BASE = "http://localhost:8000"

def pages(**filters):
    offset = 0
    while True:
        p = requests.get(f"{BASE}/v1/indicators",
                         params={**filters, "limit": 100, "offset": offset}).json()["payload"]
        yield from p["indicators"]
        offset += p["count"]
        if offset >= p["total"] or p["count"] == 0:
            break

both = [i["name"] for i in pages() if i.get("both_engines")]
print(len(both), both)

3.3 Cómo es una entrada

Una primitiva registrada (extracto real de 08, entrada sma):

{
  "name": "sma",
  "engines": { "vectorta": { "batch": true, "stream": true },
               "nautilus": { "native": true, "stream": true } },
  "project": { "registered": true, "primitive": "sma", "phase": "A" },
  "capability": { "level": "approved", "computable_via_api": true,
                  "approval": { "scope": ["benchmark", "research"], "production": false, "…": "…" },
                  "…": "…" },
  "first_valid_rule": "period_minus_1",
  "params": [ { "name": "period", "type": "int", "required": true, "example": 10 } ],
  "…": "…"
}

Una entrada solo de motor (extracto real de 08):

{
  "name": "absolute_strength_index_oscillator",
  "engines": { "vectorta": { "batch": true, "stream": true },
               "nautilus": { "native": false, "stream": false } },
  "project": { "registered": false, "primitive": null, "phase": null },
  "capability": { "level": "discovered", "…": "…" },
  "both_engines": false
}

Los 15 indicadores presentes en ambos motores en el ejemplo 08 son: sma, ema, rma, rsi, bbands, atr, donchian, wma, hma, macd, keltner_sma, stochrsi, dmi, psychological_line y vertical_horizontal_filter.

El bloque engines del payload

Además de la lista, la respuesta trae payload.engines, un resumen por motor. Del ejemplo real: VectorTA 0.2.8, kernel scalar, catalog: {indicators: 346, batch: 342, stream: 334}; Nautilus 1.231.0, catalog: {indicators: 36, native: 36, stream: 35}. Los contadores batch/stream/native (p. ej. native: 8, custom_reference: 31 en el batch de VectorTA) se refieren solo a las 39 primitivas registradas: cuántas se sirven con la función nativa del motor y cuántas con una implementación de referencia propia.


4. La ficha de un indicador: GET /v1/indicators/{name}

Acepta tres formas de nombre:

  • el nombre de la primitiva del proyecto (rsi, bbands, keltner_sma);
  • el nombre nativo de cualquiera de los motores (BollingerBands, AroonOscillator, wilders);
  • variantes de estilo (aroon_oscillator, bollingerbands).
curl -sS "$BASE/v1/indicators/rsi" | jq '.payload.indicator | {name, first_valid_rule, both_engines}'
curl -sS "$BASE/v1/indicators/wilders" | jq -r '.payload.indicator.name'      # -> rma
curl -sS -o /dev/null -w '%{http_code}\n' "$BASE/v1/indicators/not_an_indicator"   # 404

Lo más valioso de la ficha es la disponibilidad por motor, que declara las variantes en lugar de esconderlas. Extracto real de 09_indicator_detail.response.json (RSI):

{
  "vectorta": {
    "batch":  { "state": "native", "variant": "canonical", "note": null },
    "stream": { "state": "native", "variant": "canonical", "note": null }
  },
  "nautilus": {
    "native": { "state": "variant", "variant": "nt_init_period_minus_1_scaled_0_1",
                "note": "Seeds at period−1 (canonical: period) and works internally in 0..1; the provider rescales it to 0..100." },
    "stream": { "state": "variant", "variant": "nt_init_period_minus_1_scaled_0_1", "note": "…" }
  }
}

Ejemplo de trading: el RSI no arranca igual en los dos motores

El RSI de Nautilus «siembra» una barra antes (period−1) que el canónico (period) y trabaja internamente en 0..1. Por eso, al calcular el RSI14 sobre NVDA 1min, el primer valor válido es el índice 14 en VectorTA y 13 en Nautilus (lo veremos en el capítulo 5). No es un fallo: es una variante declarada.

Cada ficha registrada incluye también example_request y example_curl listos para copiar. El real de 09:

curl -sS -X POST "$BASE/v1/indicators" -H 'Content-Type: application/json' -d '{"schema_version": "v1", "payload": {"data": {"asset_id": "NVDA", "timeframe": "1d", "window": {"start": null, "end": null, "mode": "reset_flat"}}, "indicator": {"indicator": "rsi", "params": {"period": 14}, "source": "close"}, "engine": "both", "engine_options": {"vectorta": {"kernel": "scalar", "use_batch": true}, "nautilus": {"adapter": "NT_FEATURES", "indicator_provider": "native"}}}}'

Una ficha solo de motor (p. ej. alma) lleva registered: false, la nota de que no es calculable por POST /v1/indicators y engines_detail con módulo y firma. Del ejemplo real 14: módulo vector_ta, función alma, batch alma_batch, clase stream AlmaStream, firma (data, period, offset, sigma, kernel=None).


5. La ayuda: GET /v1/help y GET /v1/help/{name}

5.1 El índice

La ayuda organiza 417 temas en tres familias:

Familia (kind) Qué contiene
indicator Primitivas registradas 39
engine_indicator Indicadores solo de motor (342 calculables por paso directo, sin garantías) 347
strategy Plantillas P01..P20, Q01..Q10 31

Filtros: kind (all, indicator, engine_indicator, strategy), q (subcadena en id y resumen) y limit (1..2000, defecto 500).

Respuesta real de GET /v1/help?limit=20 (12_help_index.response.json), recortada:

{
  "payload": {
    "count": 20, "total": 417,
    "filters": { "kind": "all", "q": null, "limit": 20 },
    "topics": [
      { "id": "sma", "kind": "indicator", "summary": "Simple moving average of `period` consecutive valid observations." },
      { "id": "ema", "kind": "indicator", "summary": "Exponential moving average; seed = SMA of the first `period` valid values, alpha = 2/(n+1)." },
      { "id": "rma", "kind": "indicator", "summary": "Wilder's moving average; seed = SMA of the first `period` valid values, alpha = 1/n." },
      "…"
    ]
  }
}

Idioma

Los temas de indicador están en inglés; los resúmenes y descripciones de estrategia conservan la redacción original (en español) del catálogo.

5.2 El dossier de un tema

La forma depende de la familia. Las claves reales de cada dossier de ejemplo:

Ejemplo kind Campos principales
13_help_indicator_rsi indicator params, inputs/outputs, first_valid_rule, availability, capability, examples (batch/stream/nautilus), example_curl, feature_roles
14_help_engine_only_alma engine_indicator engines, engines_detail, examples, capability (discovered + promotion_note), note
15_help_strategy_p01 strategy params, defaults, parameter_grid, constraints, features, policy_params, warmup_rule, example_curl
curl -sS "$BASE/v1/help/rsi" | jq -r '.payload.examples.batch'

Los ejemplos reales de código del dossier 13:

# batch (VectorTA)
import vector_ta as va
out = va.rsi(close, period=14)  # runnable with the example parameters

# stream (VectorTA)
stream = va.RsiStream(period=14)
result = stream.update(close_t)

# nautilus
from nautilus_trader.indicators import RelativeStrengthIndex
indicator = RelativeStrengthIndex(...)  # fill the required parameters marked with `...`
indicator.update_raw(close)

Y feature_roles dice qué estrategias lo usan: P02 (depende de period), P17 (de rsi_period), Q08 y Q08B.

curl -sS "$BASE/v1/help/P01" | jq '.payload | {kind, name, defaults, parameter_grid, warmup_rule}'

Respuesta real (dossier 15, recortada):

{
  "kind": "strategy",
  "name": "Cruce de medias EMA",
  "defaults": { "fast": 20, "slow": 50 },
  "parameter_grid": { "fast": [5, 10, 20, 40], "slow": [50, 100, 150, 200] },
  "constraints": [ { "rule": "fast < slow", "message": "slow debe ser estrictamente mayor que fast.", "fields": ["fast", "slow"] } ],
  "warmup_rule": "max(first_valid_index de las features implicadas) bajo el proveedor efectivo; se documenta por corrida."
}

Su example_curl es un POST /v1/backtest completo con plantilla en línea (template_id + params), NVDA 1d y cost_scenario: "zero".

curl -sS "$BASE/v1/help/alma" | jq '.payload | {kind, engines, note}'
{
  "kind": "engine_indicator",
  "engines": { "vectorta": true, "nautilus": false },
  "note": "This indicator is not registered as a project primitive; not computable via POST /v1/indicators."
}

Su capability.promotion_note explica qué haría falta para promocionarlo: fórmula canónica, params, first_valid, warmup, test de fidelidad, paridad entre carriles y alta en el registro de aprobación (docs/CAPABILITY_LEVELS.md).

Un tema desconocido nunca es un 200 vacío: devuelve 404 HELP_TOPIC_NOT_FOUND.

curl -sS "$BASE/v1/help/no_such_topic" | jq '.error.code'   # "HELP_TOPIC_NOT_FOUND"

6. Flujo recomendado antes de pedir un cálculo

sequenceDiagram
    participant T as Trader
    participant API as bench-api
    T->>API: GET /v1/help?kind=strategy&q=RSI
    API-->>T: P02, P17, …
    T->>API: GET /v1/help/P02
    API-->>T: defaults, grid, constraints, example_curl
    T->>API: GET /v1/indicators/rsi
    API-->>T: first_valid_rule, availability (variante en Nautilus)
    T->>API: POST /v1/backtest (capítulo 3)

Resumen

  • Las plantillas (31) son recetas inmutables; las instancias son plantilla + params con id determinista.
  • GET /v1/catalog/strategies filtra por category, milestone y status; supported_by responde hoy 501 por honestidad.
  • GET /v1/indicators enumera 386 entradas: 375 ejecutables por la API: 33 con fórmula canónica y paridad verificada y 342 por paso directo al motor sin garantías canónicas; 11 no ejecutables con motivo. Solo las que tienen contrato (implemented/verified/approved) dan garantías.
  • counttotalcatalog_size; pagina con limit/offset.
  • La ficha GET /v1/indicators/{name} resuelve nombres nativos y declara las variantes por motor.
  • GET /v1/help (417 temas) y GET /v1/help/{name} dan ejemplos de Python y un example_curl listo para copiar.

Para practicar

  1. ¿Cuántas plantillas quant hay en el hito H4? Usa category y milestone.
  2. Lista los indicadores con carril en los dos motores y comprueba que son 15.
  3. Pide GET /v1/indicators/BollingerBands. ¿A qué primitiva canónica resuelve?
  4. Elige un indicador discovered e intenta calcularlo con POST /v1/indicators. ¿Qué error recibes y qué dice su promotion_note?
  5. Usando GET /v1/help/P02, identifica qué parámetros son de política y cuáles de feature. ¿Qué implica para una derivación que cambie los umbrales 30/70 → 25/75?