Primeros pasos con la API de backtesting y análisis (bench-api)¶
Qué vas a aprender¶
- Qué es
bench-apiy por qué existe una API «intermedia» entre tú y los dos motores (VectorTA y Nautilus). - Cómo levantar el servicio con Docker Compose y comprobar que está vivo.
- Dónde está la documentación interactiva (Swagger UI y ReDoc) y el contrato formal.
- Cómo es el sobre común (envelope) que envuelve cada petición y cada respuesta.
- Qué significan los códigos HTTP 200 / 202 / 507 en
POST /v1/backtest(decisión D-33). - Cómo leer un error: los 36 códigos del contrato (37 en el servidor, que añade
ENDPOINT_NOT_IMPLEMENTED) y el camporetriable. - Para qué sirven
/healthy/doctor.
1. La idea: un contrato común, dos motores¶
Imagina que tienes dos calculadoras financieras muy distintas:
- VectorTA es una calculadora vectorial en Rust: recorre el array entero de barras de golpe. Es rapidísima, ideal para barridos de parámetros.
- Nautilus es un simulador de mercado orientado a eventos: órdenes, cuenta, comisiones, fills. Es más lento, pero es el que tiene «realismo de ejecución».
Si cada motor tuviera su propia API, comparar resultados sería como comparar precios en dos monedas sin tipo de cambio. bench-api resuelve eso con un único contrato: los datos, la estrategia, la ventana, los costes, las tablas de salida, las etapas cronometradas y los estados son idénticos para los dos motores. Lo que es propio de cada motor vive aislado en engine_options.<motor> y, por regla, nunca cambia la semántica del cálculo, solo el cómo.
flowchart LR
C[Cliente<br/>curl / Python / notebook] -->|HTTP JSON<br/>envelope v1| API[bench-api<br/>FastAPI :8000]
API --> R[finazbench.runner]
R --> V[VectorTA<br/>VTA_CPU_LEDGER]
R --> N[Nautilus<br/>NT_FEATURES / NT_ONLINE / NT_INTENT_REPLAY]
R --> RES[(results/runs/«run_id»/<br/>results/jobs/«job_id»/)]
API -. futuro .-> RP[(Redpanda<br/>finaz.*.v1)]
Los diez principios del contrato (docs/API.md §1) se pueden resumir en cuatro frases que conviene memorizar:
| Principio | En una frase |
|---|---|
| Un contrato, dos motores | Lo común es común; lo específico va en engine_options. |
| Firma completa | Todo resultado lleva data_hash, formula_version, params, contrato de ejecución, cache_mode, output_mode, versiones y huella de máquina. Sin firma no hay comparación. |
| Nunca un speedup sin paridad | Un «VectorTA es N veces más rápido» solo se publica si los dos motores dan el mismo resultado (parity=PASS) y las firmas coinciden. |
| Nada de números inventados | Lo que el runner no mide se devuelve como null o "<medido>", nunca como un cero ni como una estimación. |
¿Por qué «intermedia»?
El contrato se llama FINAZ intermediate API — v1. Es intermedia porque está entre tú y los motores del banco de backtesting. No es la única API de la plataforma: la API de plataforma (:18300, /platform/v1/...) controla las corridas causales; esta (:8000, /v1/...) lanza backtests, paridad, barridos e indicadores. Son dos puertas con roles distintos, no dos generaciones. Además está diseñada para que el payload de cada mensaje sea exactamente el mensaje que viajará mañana por los tópicos de Redpanda.
2. Levantar el servicio¶
bench-api es un servicio del docker-compose.yml del repositorio, dentro del perfil bench-api. Los perfiles hacen que un docker compose up sin argumentos no arranque nada nuevo, de modo que no interfiere con servicios que ya estén en marcha.
# Desde la raíz del repositorio FinazTradingEngine
docker compose --profile bench-api up -d bench-api
# ¿Está vivo?
curl -s localhost:8000/health | jq
Qué hace esa definición de servicio (resumida de docker-compose.yml):
| Aspecto | Valor | Por qué |
|---|---|---|
| Imagen | ${BENCH_IMAGE:-finaz/bench:0.2.0-x86-64-v3} |
La misma imagen que los perfiles dev y bench. |
| Puerto | 8000:8000 |
Todas las URLs de este capítulo usan http://localhost:8000. |
| Volúmenes | ./data:/data:ro, ./cache:/cache, ./results:/results, ./runs:/runs |
Los datos se montan solo lectura; los resultados se escriben fuera del contenedor. |
| Comando | uvicorn finazbench.api.main:app --host 0.0.0.0 --port 8000 --workers 1 |
Un solo worker y sin --reload. |
| CPU/memoria | cpuset ${BENCH_CPUSET:-0-3}, cpus ${BENCH_CPUS:-4}, mem_limit ${BENCH_MEM_LIMIT:-4g} |
Presupuesto parametrizable en .env. |
Un solo worker, a propósito
El proceso que sirve la petición es el mismo que mide. Un recargador automático o varios workers falsearían los tiempos por etapa y la atribución de memoria. No «optimices» esto añadiendo --workers 4: estarías rompiendo el banco de medida.
Para pararlo, nómbralo siempre:
Cuidado con docker compose down a secas
El compose conserva, sin perfil, los servicios heredados vectorta-service (:8001) y nautilus-service (:8002), que atienden a clientes antiguos. Un docker compose down sin argumentos los pararía también.
3. Documentación interactiva: Swagger, ReDoc y OpenAPI¶
FastAPI genera la documentación a partir del propio código, así que siempre está sincronizada con lo que el servidor acepta:
| URL | Qué es | Cuándo usarla |
|---|---|---|
| http://localhost:8000/docs | Swagger UI: formulario interactivo, botón Try it out | Para probar una ruta sin escribir curl. |
| http://localhost:8000/redoc | ReDoc: la misma especificación, en formato de lectura | Para leer esquemas largos (el de /v1/backtest lo es). |
| http://localhost:8000/openapi.json | Especificación OpenAPI en JSON | Para generar clientes o validar en CI. |
| http://localhost:8000/ | Índice mínimo | Te dice versión, schema_version y dónde está el contrato. |
La raíz devuelve (código de finazbench/api/main.py):
{
"service": "bench-api",
"api_version": "<versión de la API>",
"schema_version": "v1",
"package_version": "<versión del paquete>",
"contract": "docs/API.md",
"openapi": "/openapi.json",
"docs": "/docs"
}
En el repositorio hay además dos documentos de referencia:
docs/API.md— el contrato en prosa (en inglés), con la justificación de cada decisión.docs/openapi_v1.yaml— la especificación formal, contra la que se validan los ejemplos deexamples/api/.
Las rutas se agrupan por etiquetas en Swagger:
| Etiqueta | Rutas | Capítulo |
|---|---|---|
ops |
GET /health, GET /doctor |
este |
capabilities |
GET /v1/capabilities |
este |
catalog |
GET /v1/catalog/strategies[/{id}], GET /v1/catalog/assets |
2 |
indicators |
GET /v1/indicators[/{name}] |
2 |
help |
GET /v1/help[/{name}] |
2 |
strategies |
POST /v1/strategies, POST /v1/strategies/{id}/derive, GET/PATCH /v1/strategies… |
3 |
compute |
POST /v1/indicators, /v1/backtest, /v1/parity, /v1/sweep, GET /v1/stats/runs |
3–6 |
jobs |
GET /v1/jobs, GET/DELETE /v1/jobs/{id}, GET /v1/jobs/{id}/results |
4, 6 |
4. El sobre común (envelope)¶
Piensa en el sobre como en un sobre postal: fuera lleva los datos de envío (quién, para qué hilo, qué versión), dentro va la carta (payload). Todas las peticiones POST y todas las respuestas de la API van dentro del mismo sobre.
4.1 Petición¶
{
"request_id": "9a4e2b10-0c3f-4c8e-b0d7-5f1e2a9c6d33",
"correlation_id": null,
"schema_version": "v1",
"payload": { "…": "cuerpo propio de cada endpoint" }
}
| Campo | Tipo | ¿Obligatorio? | Significado |
|---|---|---|---|
request_id |
UUIDv4 | Opcional en la petición (si falta, lo genera el servidor); siempre en la respuesta | Identifica una petición. Se devuelve tal cual. |
correlation_id |
string o null |
Opcional | Hilo de negocio que atraviesa varios mensajes. Hoy se propaga sin interpretar; en Redpanda será la clave de correlación. |
schema_version |
"v1" literal |
Sí | Cualquier otro valor ⇒ 400 SCHEMA_VERSION_UNSUPPORTED. |
payload |
objeto | Sí | Lo único que cambia entre HTTP y Redpanda. |
4.2 Respuesta¶
La respuesta repite los tres campos de cabecera y añade dos más al mismo nivel que payload. Este es el sobre real de la respuesta de examples/api/01_backtest_p01_both.response.json (sin el payload):
{
"request_id": "9a4e2b10-0c3f-4c8e-b0d7-5f1e2a9c6d33",
"correlation_id": null,
"schema_version": "v1",
"payload": { "…": "…" },
"served_at_ns": 1789657938275241827,
"server": { "api_version": "1.0.0", "image_tag": "finaz/bench:0.2.0", "git_sha": null }
}
served_at_ns: instante en que se sirvió, en nanosegundos desde la época Unix.server: qué versión de API e imagen respondió. Sigit_shano se conoce, esnull(no se inventa).
Cuando hay error, payload se sustituye por error (sección 6).
4.3 El mismo sobre, mañana en Redpanda¶
sequenceDiagram
participant Cli as Cliente
participant API as bench-api (HTTP)
participant T as Tópico finaz.backtest.request.v1
participant W as Consumidor
Cli->>API: POST /v1/backtest {request_id, schema_version, payload}
API-->>Cli: 200 {request_id, payload, served_at_ns, server}
Note over Cli,W: Mañana (misma carta, otro cartero)
Cli->>T: key = correlation_id ?? request_id<br/>value = envelope completo
T->>W: mismo payload, byte a byte
| Operación HTTP | Tópico de petición | Tópico de resultado | Clave de partición |
|---|---|---|---|
POST /v1/indicators |
finaz.indicator.request.v1 |
finaz.indicator.result.v1 |
asset_id |
POST /v1/backtest |
finaz.backtest.request.v1 |
finaz.backtest.result.v1 |
strategy_id |
POST /v1/parity |
finaz.parity.request.v1 |
finaz.parity.result.v1 |
strategy_id |
POST /v1/sweep |
finaz.sweep.request.v1 |
finaz.sweep.result.v1 |
job_id |
GET /v1/jobs/{id} |
— | finaz.job.progress.v1 |
job_id |
Las rutas de solo lectura (/v1/capabilities, /v1/catalog/*, /v1/indicators en GET, /v1/help, /v1/jobs, /v1/stats/runs, /health, /doctor) son solo HTTP.
Las rutas GET no llevan sobre de petición
Un GET no tiene cuerpo, así que no hay sobre que enviar. Pero la respuesta de las rutas /v1/... sí viene envuelta (payload). Las dos excepciones son /health y /doctor, que son sondas de infraestructura y responden sin sobre.
4.4 Validación estricta: un typo es un error¶
Los modelos de petición prohíben campos desconocidos (extra="forbid" en RequestModel). Si escribes "initial_cahs" en lugar de "initial_cash", la petición falla; no se ejecuta con el valor por defecto. El motivo es sutil pero importante: si se ejecutara, la respuesta llevaría una firma que no corresponde a lo que creías pedir, y cualquier comparación posterior por firma sería falsa.
5. 200, 202 o 507: la decisión D-33¶
POST /v1/backtest puede tardar milisegundos o minutos según la ventana y el motor. La decisión D-33 (docs/DECISIONES.md, 2026-09-17) fija cómo responde:
| Código | Cuándo | Qué recibes |
|---|---|---|
| 200 | El caso cabe en el budget declarado (o no declaras ninguno) |
El resultado completo, síncrono. |
| 202 | Excede el budget declarado pero está dentro de la política de recursos (max_estimated_bar_candidate_evaluations_default = 1e8) |
Un acuse con job_id, resource_estimate y poll. El resultado llega luego por /v1/jobs/{id}/results. |
| 507 | Excede la política de recursos | 507 RESOURCE_LIMIT, antes de ejecutar y sin recortar la ventana en silencio. |
flowchart TD
A[POST /v1/backtest] --> B[Estimar evaluaciones<br/>barra x candidato]
B --> C{¿Supera la política<br/>1e8 evaluaciones?}
C -- sí --> E[507 RESOURCE_LIMIT<br/>no se ejecuta]
C -- no --> D{¿Cabe en el budget<br/>declarado?}
D -- sí / sin budget --> F[200 resultado síncrono]
D -- no --> G[202 + job_id<br/>se ejecuta en segundo plano]
G --> H["GET /v1/jobs/{id}/results<br/>payload.backtest"]
Ejemplo real: examples/api/07_backtest_p01_queued_202.request.json pide un presupuesto imposible, "budget": {"max_wall_seconds": null, "max_memory_bytes": 1}, para forzar el camino en cola. La respuesta real (recortada) es:
{
"request_id": "7c1d2e3f-4051-4627-8899-aabbccddeeff",
"schema_version": "v1",
"payload": {
"job_id": "job_FCF18574F7",
"status": "QUEUED",
"requested_candidates": 1,
"effective_candidates": 1,
"materialized_strategy_ids": ["P01-dc9260"],
"resource_estimate": {
"estimated_peak_bytes": 1032192,
"block_size": 504,
"workers": 1,
"estimated_bar_candidate_evaluations": 504,
"policy_max_estimated_bar_candidate_evaluations_default": 100000000
},
"poll": "/v1/jobs/job_FCF18574F7"
},
"served_at_ns": 1789658011544685030,
"server": { "api_version": "1.0.0", "image_tag": "finaz/bench:0.2.0", "git_sha": null }
}
Cómo tratar el 202 en tu cliente
No es un error: es «te lo hago, pero no te quedes esperando en la línea». Guarda job_id y consulta poll hasta que el estado sea terminal. Lo vemos a fondo en el capítulo 4 y el capítulo 6.
POST /v1/sweep responde siempre 202 (un barrido es asíncrono por naturaleza), y también puede devolver 507 con la misma política.
6. Errores: formato y catálogo¶
6.1 Formato común¶
Todo error tiene la misma forma: el sobre, con error en lugar de payload. Ejemplo real (examples/api/91_error_strategy_params_invalid.response.json), que se produce al pedir un P01 con fast=20 y slow=20:
{
"request_id": "1b2c3d4e-5f60-4718-a92b-c3d4e5f60718",
"correlation_id": null,
"schema_version": "v1",
"error": {
"code": "STRATEGY_PARAMS_INVALID",
"message": "Los parámetros no cumplen el schema del template P01.",
"details": {
"template_id": "P01",
"violations": [
{ "field": "slow", "rule": "fast < slow", "value": 20,
"related": { "fast": 20 },
"message": "slow debe ser estrictamente mayor que fast." }
]
},
"retriable": false,
"docs": "docs/API.md#48-validación-de-params"
}
}
| Campo | Para qué |
|---|---|
code |
Identificador estable, para programar contra él (if code == "…"). |
message |
Texto legible (en español). |
details |
Datos estructurados: qué campo, qué regla, qué valor. |
retriable |
true solo en RESOURCE_LIMIT, TIME_BUDGET_EXCEEDED e INTERNAL_ERROR. |
docs |
Ancla de docs/API.md con la regla incumplida. |
Una sola puerta de salida
Todos los errores pasan por manejadores globales (finazbench/api/errors.py). Así ninguno puede «escaparse» como un 200 con ceros: un fallo no previsto sale como 500 INTERNAL_ERROR, nunca como una cifra.
6.2 Los 36 códigos del contrato, agrupados¶
El catálogo de docs/API.md §8 tiene 36 códigos. Agrupados por familia HTTP:
| Código | HTTP | Cuándo |
|---|---|---|
SCHEMA_VERSION_UNSUPPORTED |
400 | schema_version distinto de "v1" |
MALFORMED_ENVELOPE |
400 | Falta payload o el JSON no parsea |
TEMPLATE_NOT_FOUND |
404 | template_id no está en el catálogo |
STRATEGY_NOT_FOUND |
404 | strategy_id o slug no registrado |
ASSET_NOT_FOUND |
404 | Activo o timeframe no disponible |
JOB_NOT_FOUND |
404 | job_id inexistente o purgado |
INDICATOR_NOT_FOUND |
404 | Indicador que no existe en ningún registro |
HELP_TOPIC_NOT_FOUND |
404 | Tema de ayuda en ninguna familia |
| Código | Cuándo |
|---|---|
STRATEGY_PARAMS_INVALID |
Params fuera de esquema o que violan una restricción |
INDICATOR_PARAMS_INVALID |
Params de indicador inválidos (o indicador no calculable) |
STRATEGY_SELECTOR_AMBIGUOUS |
strategy_id y template_id a la vez |
DATA_SELECTOR_AMBIGUOUS |
asset_id y assets a la vez |
COSTS_AMBIGUOUS |
cost_scenario y costs a la vez |
SWEEP_SELECTOR_AMBIGUOUS |
grid y candidates a la vez |
DERIVE_NO_CHANGE |
Un /derive que no cambia ningún valor |
STRATEGY_IMMUTABLE_FIELD |
Un PATCH que intenta tocar params/template_id |
ENGINE_OPTION_UNKNOWN |
Clave desconocida en engine_options |
ENGINE_OPTION_UNSUPPORTED_VALUE |
Valor que no existe en el contrato |
ENGINE_OPTION_UNAVAILABLE |
Valor válido, pero no disponible en este build |
ENGINE_OPTION_ALTERS_CONTRACT |
La opción cambiaría la semántica SIM-S |
PROFILE_OVERSUBSCRIBED |
workers>1 e internal_threads>1 a la vez |
PARITY_SIDES_INVALID |
Menos de dos lados o firmas de contrato distintas |
UNIVERSE_NOT_AVAILABLE |
Q01–Q09 sin panel multiactivo descargado |
INSUFFICIENT_DISTINCT_CANDIDATES |
La rejilla no da los K candidatos distintos pedidos |
| Código | Cuándo |
|---|---|
STRATEGY_NAME_TAKEN |
Nombre ya usado por otra instancia |
STRATEGY_SLUG_TAKEN |
Slug ya usado |
STRATEGY_ID_AMBIGUOUS |
Prefijo de id que casa con varias instancias |
STRATEGY_FORMULA_STALE |
La instancia usa una formula_version antigua |
CHECKPOINT_INCOMPATIBLE |
mode=continue con checkpoint ausente o incompatible |
INSUFFICIENT_HISTORY |
No hay barras suficientes para el warmup |
| Código | HTTP | Cuándo |
|---|---|---|
LANE_UNSUPPORTED |
501 | lane distinto de SIM-S |
STRATEGY_UNSUPPORTED_BY_ADAPTER |
501 | Combinación declarada UNSUPPORTED |
MISSING_OPTIONAL_DEPENDENCY |
501 | Plantilla que exige una dependencia ausente (p. ej. Q08B y catboost) |
TIME_BUDGET_EXCEEDED |
504 | Se superó budget.max_wall_seconds |
RESOURCE_LIMIT |
507 | La estimación previa supera la política |
INTERNAL_ERROR |
500 | Fallo inesperado |
Un código extra en el código: ENDPOINT_NOT_IMPLEMENTED
El mapa de códigos del servidor (ERROR_HTTP_STATUS en finazbench/api/schemas_v1.py, 37 entradas) tiene uno más que el catálogo de docs/API.md §8 (36): ENDPOINT_NOT_IMPLEMENTED (501). Se usa, por ejemplo, si pides GET /v1/catalog/strategies?supported_by=…: filtrar por adaptador exigiría la matriz real de soporte, y responder con una lista «plausible» sería presentar una conjetura como hecho.
6.3 Dos errores que parecen iguales y no lo son¶
Los ejemplos 90 y 92 enseñan una distinción clave:
{
"error": {
"code": "ENGINE_OPTION_ALTERS_CONTRACT",
"message": "Una latencia ≠ 0 altera el contrato SIM-S.",
"details": {
"field": "nautilus.latency_model",
"value": { "base_ms": 1 },
"contract": "SIM-S v1",
"why": "Sobre datos de barras produce el cierre de la barra siguiente (medido: 111), no la apertura (110)."
},
"retriable": false
}
}
Nunca se «arreglará»: añadir latencia sobre barras cambiaría el precio de ejecución, y eso cambia qué se calcula.
{
"error": {
"code": "ENGINE_OPTION_UNAVAILABLE",
"message": "El kernel avx2 no está disponible en este build de VectorTA.",
"details": {
"field": "vectorta.kernel",
"value": "avx2",
"accepted": ["scalar", "avx2", "avx512", "auto"],
"available": ["scalar", "auto"],
"hint": "usa kernel=scalar, que es lo que se ejecuta hoy"
},
"retriable": false
}
}
El contrato acepta avx2 (accepted), pero este wheel no lo trae (available). Se arregla con otra imagen.
7. Sondas: /health y /doctor¶
7.1 /health: ¿estás vivo?¶
Sonda barata para Compose y balanceadores. Sin sobre. Según el código (finazbench/api/routers/system.py) devuelve:
{
"status": "ok",
"api_version": "1.0.0",
"schema_version": "v1",
"uptime_seconds": 1234,
"engines": { "vectorta": "ready", "nautilus": "ready" },
"data_mounted": true,
"cache_writable": true,
"results_writable": true,
"service": "bench-api",
"python": "3.12.x"
}
(Forma tomada del código; no hay respuesta real registrada en examples/api/ y el servicio no corre en el servidor de producción. Valores de uptime_seconds y versión de Python ilustrativos.)
Si un motor no carga o cache/results no son escribibles, responde 503 con "status": "degraded". La razón: una instancia que arranca pero no puede escribir resultados perdería las corridas en silencio; el balanceador debe poder sacarla de rotación.
7.2 /doctor: inventario completo¶
Inventario del entorno desde dentro del contenedor, de solo lectura. Es lo que se adjunta a cualquier medición: versiones de librerías, CPU, cgroup, variables de hilos (OMP_NUM_THREADS=1, etc.), kernel activo de VectorTA, volúmenes. Reutiliza tools/doctor.py, así que el JSON por HTTP y el de línea de comandos son el mismo documento.
curl -s localhost:8000/doctor | jq '{python, libraries, vectorta}'
# Sondeo real de kernels (llama a la librería; los flags de CPU no bastan)
curl -s 'localhost:8000/doctor?probe_vectorta_kernels=true' | jq .vectorta_kernels
| Parámetro | Efecto |
|---|---|
probe_imports=true |
Importa las librerías y lista su API pública. |
probe_vectorta_kernels=true |
Ejecuta la prueba de humo de kernels: comprueba qué kernel SIMD corre de verdad. |
7.3 /v1/capabilities: qué puede hacer esta instalación¶
Es lo primero que debería consultar un cliente «para no pedir lo imposible»: matriz estrategia × adaptador (SUPPORTED, VARIANT, UNSUPPORTED con motivo), versiones, kernels aceptados y disponibles, modos de caché y perfiles.
curl -sS localhost:8000/v1/capabilities | jq '.payload.engines.vectorta | {active_kernel, kernel_available}'
8. Tu primera petición completa¶
BASE=http://localhost:8000
cd FinazTradingEngine # el repo, para tener examples/api a mano
curl -sS -X POST "$BASE/v1/backtest" \
-H 'Content-Type: application/json' \
-d @examples/api/01_backtest_p01_both.request.json \
| jq '.payload | {status, parity: .parity.verdict, speedup: .speedup.published}'
import json, requests
BASE = "http://localhost:8000"
with open("examples/api/01_backtest_p01_both.request.json") as f:
body = json.load(f)
r = requests.post(f"{BASE}/v1/backtest", json=body, timeout=600)
r.raise_for_status()
env = r.json()
if r.status_code == 202:
print("En cola:", env["payload"]["poll"])
else:
p = env["payload"]
print(p["status"], p["parity"]["verdict"], p["speedup"]["published"])
En el ejemplo regenerado, la respuesta tiene status: "PASS", parity.verdict: "PASS" y speedup.published: true. En el capítulo 3 la desmontamos campo a campo.
Resumen¶
bench-apies un servicio FastAPI en:8000, perfilbench-apide Compose, con un solo worker porque también mide.- La documentación viva está en
/docs(Swagger),/redocy/openapi.json; el contrato razonado endocs/API.md. - Todo
POSTy toda respuesta/v1va en un sobre:request_id,correlation_id,schema_version: "v1",payload(másserved_at_nsyserveren la respuesta). - D-33:
POST /v1/backtestresponde 200 si cabe en el presupuesto, 202 + job si no cabe pero está dentro de la política (1e8 evaluaciones), 507 si la supera. - Los errores comparten forma (
code,message,details,retriable,docs); el contrato define 36 códigos y el servidor emite 37 (añade501 ENDPOINT_NOT_IMPLEMENTED). /healthdice si el servicio está sano (503 si no);/doctordice en qué entorno estás midiendo.
Para practicar¶
- Levanta el servicio y abre http://localhost:8000/docs. Localiza el esquema de
POST /v1/backtesty cuenta cuántos bloques de primer nivel tiene supayload. - Envía
01_backtest_p01_both.request.jsoncambiandoschema_versiona"v2". ¿Qué código HTTP y quéerror.coderecibes? - Añade un campo inventado (
"foo": 1) dentro depayload.execution. ¿Se ejecuta? ¿Por qué es deseable que no? - Envía
07_backtest_p01_queued_202.request.jsony consulta la URL depollhasta que el job termine. - Compara
acceptedyavailableen el errorENGINE_OPTION_UNAVAILABLE. Explica con tus palabras por qué no es unENGINE_OPTION_ALTERS_CONTRACT.