API de estrategias

Compila, valida, inspecciona, recupera y elimina estrategias Java o QTScript (beta) a través de la API REST.

Compila una estrategia (Java, o QTScript en beta), comprueba que realmente funciona, lista/inspecciona/elimina lo que has registrado, y recupera su código fuente.

Esta página documenta los recursos REST de estrategias. Para el código fuente Java en sí — clases base, señales de ejecución e información, parámetros de orden avanzados y metadatos de gráfico — consulta Programar estrategias en Java.

MétodoRutaPropósito
POST/strategyCompilar y registrar
GET/strategiesListar tus estrategias registradas
GET/strategy/{strategyId}Obtener una, incluido su estado de validación
POST/strategy/{strategyId}/validateComprobar que realmente funciona
GET/strategy/{strategyId}/codeRecuperar el código fuente registrado
DELETE/strategy/{strategyId}Liberarla

Compilar una estrategia

POST /strategy — el cuerpo es el código fuente en crudo, Content-Type: text/plain. Java es la vía establecida; QTScript es un lenguaje nuevo y compacto en beta (consulta QTScript más abajo, y la guía de QTScript).

curl -X POST https://api.qtsurfer.net/v1/strategy 
  -H "Authorization: Bearer $TOKEN" 
  -H "Content-Type: text/plain" 
  --data-binary @MyStrategy.java
{
  "strategyId": "2ul144qe9tlwzu5anhwvc6",
  "declaredProperties": [
    {"name": "rsi.period", "description": "RSI period", "defaultValue": "14",
     "reflected": true, "min": 2, "max": 50, "step": 1},
    {"name": "enabled", "description": "Enabled", "reflected": true}
  ]
}

Esto responde a una única pregunta: si el fuente es válido. Compila, registra y devuelve el id — nada más. Un 200 significa que el fuente se analizó y compiló, no que vaya a funcionar: lo que solo aparece cuando la estrategia monta sus indicadores (en QTScript, una ventana sobre un indicador que no está registrado) lo halla validate. Si la clase realmente funciona se comprueba con validate; todo lo que se sabe de una estrategia, incluida su validación, se lee de GET /strategy/{strategyId}.

Para Java, el strategyId se deriva de lo que el código significa, no de cómo está escrito. Un comentario, una línea en blanco, reindentar, reordenar imports o mover un método de sitio devuelven todos el mismo id — no has creado una segunda estrategia. Renombrar una variable, cambiar la capitalización de un identificador o reordenar campos/sentencias devuelve uno distinto. (Para QTScript las reglas son distintas — consulta más abajo). Dos consecuencias:

  • reenviar una estrategia Java que solo has reformateado es gratis — recuperas el id que ya tenías, junto con cualquier validación ya registrada contra él;
  • el id no dice nada sobre el comportamiento. Dos fuentes que calculan lo mismo por medios distintos son dos estrategias, ya que decidir lo contrario significaría decidir la equivalencia de programas.

QTScript (beta)

La guía de QTScript cubre el lenguaje en una sola página, con ejemplos. En resumen: QTScript elimina la ceremonia alrededor de una estrategia — paquete, imports, clase, clase base, anotaciones de propiedades — y mantiene cada cuerpo { } como Java tal cual. Se distingue de Java por su primer token: un fichero QTScript empieza por strategy. Los espacios en blanco y comentarios (// o /* */) antes de esa palabra se ignoran, así que puede haber una descripción encima del fichero. El mismo endpoint lo acepta, y una estrategia QTScript registrada se usa igual que cualquier otra.

strategy MiniKline kline

param umbralCompra = 30
param umbralVenta = 70

setup:
  rsi(14) window m1 {
    if (actual < umbralCompra) emitBuy(price);
    if (actual > umbralVenta) emitSell(price);
  }
  • strategy Nombre [kline|funding] elige la fuente de datos (ticker por defecto) — consulta Fuentes de datos para qué puede correr cada una.
  • Un 400 lleva entradas Line N, Column M: contra tu propio fuente.
  • El strategyId sale del texto, porque la indentación forma parte de la gramática. Una marca de orden de bytes (BOM), el estilo de saltos de línea, los espacios en blanco al final de línea y las líneas en blanco antes de la primera y después de la última se ignoran; cualquier otra cosa — un comentario, la indentación, una línea en blanco intermedia — da un id distinto.
  • Un fallo mientras corre una estrategia QTScript se reporta contra tu fuente, como QTScript line 6: Index 2 out of bounds for length 1.

declaredProperties — DeclaredProperty

El vocabulario de claves de parámetro que un barrido o una ejecución pueden usar y que se sabe que esta estrategia acepta — establecido sin construir la estrategia, de modo que quien la llama puede detectar una clave mal escrita antes de enviar un barrido en lugar de descubrirlo solo por un rechazo. Best-effort, no exhaustivo: una propiedad registrada de forma imperativa (por ejemplo, a través de un RiskConfig adjunto) necesita una instancia viva para descubrirse y no aparecerá aquí — un nombre ausente de esta lista puede seguir siendo válido.

CampoNotas
namela clave que usa un mapa de parámetros de barrido o ejecución para esta propiedad
descriptionetiqueta legible, tal como se declaró
defaultValueel valor por defecto declarado, como cadena. Ausente, no null, cuando no se declaró ninguno
reflectedtrue — un valor se inyecta en el campo de la estrategia; false — solo disponible a través del mapa de propiedades
min, max, steplímites de rango/barrido sugeridos, si se declararon. Solo orientativos, nunca validados

Errores: 400 no es válido — el mensaje lleva los diagnósticos, no se registra nada · 429 demasiadas compilaciones en curso, reintenta más tarde.

Límites de tamaño de la petición

Todo endpoint que lee un cuerpo de petición lo limita, y el fuente de una estrategia es el cuerpo más grande que acepta la API. Un cuerpo por encima del límite se rechaza con 413 y el error JSON habitual de la API, antes de leerlo o compilarlo, y el mensaje nombra el límite:

{"code": 413, "message": "The request body is larger than this endpoint accepts (32768 bytes at most)."}
PeticiónLímite del cuerpo
POST /strategy — el fuente32 KiB
POST /backtest/{exchangeId}/{type}/execute, POST /backtest/{exchangeId}/{type}/executeSweep/{requestId}8 KiB
POST /strategy/{strategyId}/live8 KiB
POST /backtest/{exchangeId}/{type}/prepare, PATCH /live/{runId}, PUT /live/{runId}/params4 KiB
POST /datasets, POST /datasets/imports1 KiB

Los datos de un conjunto de datos no viajan en un cuerpo de petición: van a una URL prefirmada, consulta Conjuntos de datos. Los límites son los mismos para todos (no son un límite del plan), así que un fuente de estrategia de más de 32 KiB no se puede registrar: acórtalo.

Comprobar que realmente funciona

POST /strategy/{strategyId}/validate

Instancia la clase compilada y la hace pasar por una serie sintética acotada, de modo que un fallo de cableado aparece aquí en lugar de en tu primer backtest real. El veredicto — superado o fallido, más cualquier aviso del motor — se registra y se sirve desde GET /strategy/{strategyId}.

Idempotente. Si ya existe un veredicto para la compilación actual, vuelve directamente con 200 y no se encola nada; en caso contrario, la comprobación se encola y esto devuelve 202. El código de estado, no el cuerpo, es lo que distingue ambos casos — un 200 también puede llevar validation: pending, dejado por una comprobación que encoló una llamada anterior. 202 significa esta llamada inició una comprobación; pending solo significa hay una comprobación pendiente. Sondea GET /strategy/{strategyId} hasta que validation deje de ser pending.

Recompilar reemplaza un veredicto — la respuesta antigua describía un bytecode que ya no se ejecutaría — que es lo que hace que esto se pueda volver a llamar tras una edición.

curl -X POST https://api.qtsurfer.net/v1/strategy/2ul144qe9tlwzu5anhwvc6/validate 
  -H "Authorization: Bearer $TOKEN"
# → 202 {"strategyId": "2ul144qe9tlwzu5anhwvc6", "validation": "pending"}

Errores: 404 no existe esa estrategia registrada para este usuario.

Obtener una estrategia

GET /strategy/{strategyId} — la respuesta es StrategyState, la misma forma que devuelve el 200 ya validado de validate.

validation: passed no significa que la estrategia sea correcta. Significa que la clase cargó y sobrevivió al primer evento de una ejecución sintética corta — un suelo, no una garantía. Cuando dryRunIncomplete es true, ese suelo es aún más bajo, porque la ejecución no llegó a terminar.

CampoNotas
validationnot_validated | pending | passed | failed
compiledAtcuándo se produjo la compilación en vivo
requiredSourcesdatos de mercado que necesita la estrategia (Ticker, KLine, FundingRate), leídos de la clase compilada. Ausente no significa “no necesita nada” — ausente significa que la plataforma no pudo determinar la respuesta sin construir la estrategia (una MultiSourceStrategy, una clase que sobrescribe getMarketDataSource(), o cualquier cosa registrada antes de que existiera este campo). Volver a registrar la fuente lo rellena
validatedAtcuándo se registró el veredicto; ausente hasta que hay uno
detailpor qué falló la validación, o por qué una comprobación encolada no ha respondido. Presente en failed, y junto a validationStalled
noticeslo que reveló la ejecución. Una lista vacía o ausente no es un certificado de salud limpio cuando dryRunIncomplete es true
noticesTruncatedavisos descartados por encima del límite; ausente cuando no hubo ninguno
dryRunIncompletela comprobación no terminó su presupuesto — se acabó el tiempo, fue rechazada (demasiadas ejecuciones inacabables ya en curso), o encontró un fallo atribuible al instrumento sintético en lugar de a la estrategia. El veredicto vale hasta donde llegó
validationStalleduna comprobación encolada lleva mucho más tiempo del habitual sin reportar. No se ha refutado nada — la comprobación simplemente no se ha ejecutado. Deja de esperar y vuelve a pedirla más tarde
_links.codepresente en un cuerpo completo (200 aquí, y el 200 ya validado de validate), ausente en el stub 202 de validate. Apunta a GET .../code — seguirlo puede seguir dando 404 para una estrategia sin fuente propio (ver más abajo)
{
  "strategyId": "6bsh31ikwkuivhtgcoa6s4",
  "validation": "passed",
  "compiledAt": "2026-08-04T16:23:04Z",
  "requiredSources": ["Ticker"],
  "validatedAt": "2026-08-04T16:24:11Z",
  "notices": [
    {"level": "WARN", "code": "indicator.bar-data-on-ticker-path",
     "message": "Indicator requires bar data but is on the ticker path",
     "provenance": "compile-dry-run"}
  ],
  "_links": {"code": {"href": "/v1/strategy/6bsh31ikwkuivhtgcoa6s4/code"}}
}

Errores: 404 no existe esa estrategia registrada para este usuario — nunca desfasado/expirado, el registro y el veredicto se almacenan de forma duradera, no en caché.

Listar tus estrategias

GET /strategies — todas las estrategias que has registrado y no has eliminado, las compiladas más recientemente primero. Nunca un 404 — un array vacío si no tienes ninguna.

Cada entrada (StrategySummary) lleva la misma procedencia compiledAt/requiredSources que StrategyState, pero no el estado de validación, de modo que listar sigue siendo barato sin importar cuántas estrategias tengas. Comprueba la validación de una en concreto con GET /strategy/{strategyId}.

GET /strategies?includeDeleted=true lista también las estrategias que has eliminado, cada una con el deletedAt en que se eliminó — útil para mantener tu propia copia de la lista sincronizada, de modo que una estrategia eliminada aparezca como eliminada en lugar de limitarse a desaparecer.

curl https://api.qtsurfer.net/v1/strategies -H "Authorization: Bearer $TOKEN"
{
  "strategies": [
    {"strategyId": "6bsh31ikwkuivhtgcoa6s4", "compiledAt": "2026-08-19T10:15:00Z", "requiredSources": ["Ticker"]},
    {"strategyId": "2ul144qe9tlwzu5anhwvc6", "compiledAt": "2026-08-12T09:02:11Z"}
  ]
}

Recuperar el código fuente

GET /strategy/{strategyId}/code — el fuente exacto enviado por última vez para este id, con espacios en blanco y comentarios incluidos: el mismo texto del que se derivó strategyId.

“Si está disponible”, no “siempre”. Una estrategia resuelta solo por referencia a través de un listado compartido/del marketplace que copiaste no lleva fuente propio, y aquí devuelve 404 — lo mismo que un strategyId que nunca registraste. Esa es la respuesta honesta en ambos casos: no hay nada que devolver.

curl https://api.qtsurfer.net/v1/strategy/2ul144qe9tlwzu5anhwvc6/code 
  -H "Authorization: Bearer $TOKEN"
# → {"strategyId": "2ul144qe9tlwzu5anhwvc6", "code": "package strategy;\npublic class..."}

Errores: 404 no existe esa estrategia registrada para este usuario, o no hay nada que leer para este id.

Eliminar una estrategia

DELETE /strategy/{strategyId} — libera el hueco en un plan con un tope de número de estrategias.

La elimina de GET /strategy/{strategyId} y GET /strategies. No se deshace reenviando el mismo fuente — eso registra una estrategia nueva, con un id nuevo. Los backtests ya ejecutados contra ella no se ven afectados: eliminarla hace que deje de contar contra tu cuenta y evita que la valides o vuelvas a ejecutar bajo este id, pero no borra lo que ya ocurrió. Solo elimina una estrategia que hayas registrado tú mismo — eliminar tu copia de un listado compartido/del marketplace nunca afecta al original.

curl -X DELETE https://api.qtsurfer.net/v1/strategy/2ul144qe9tlwzu5anhwvc6 
  -H "Authorization: Bearer $TOKEN"
# → {"strategyId": "2ul144qe9tlwzu5anhwvc6", "deleted": true}

Errores: 404 no existe esa estrategia registrada para este usuario.