Saltar al contenido

Un request.security sobre otro símbolo: qué necesita el backtest

6 min de lectura

Una estrategia que opera un mercado pero filtra con otro necesita las barras de ese segundo mercado, y el CSV de tu gráfico no las tiene. Qué necesita el motor de PineForge para ejecutar un script así, qué hace cuando faltan las barras y qué aportan la imagen Docker y los servidores MCP.

Actualizado el 2026-10-04: desde la versión 1.1.0, la imagen pineforge-release instala las barras de otros símbolos para request.security a partir del índice JSON al que apunta PINEFORGE_SYMBOL_FEEDS, con un feed por cada temporalidad solicitada y sin agregación (motor #327, pineforge-release #23; consulta el changelog de la 1.1.0 del motor). El resto del artículo se mantiene tal como se publicó el 2026-10-02, para la versión 1.0.1.

Tu estrategia opera una acción, pero solo compra mientras el Nasdaq está en tendencia alcista. En TradingView eso es una línea, y funciona, porque TradingView tiene las barras de todos los símbolos. Un backtester que se ejecuta sobre tu CSV solo tiene las del gráfico. Pídele QQQ y tiene tres opciones: negarse, detenerse o calcular en silencio el filtro sobre el mercado equivocado.

PineForge elige la segunda, y solo cuando no le queda otra: dale al motor las barras de QQQ y el script se ejecuta.

La línea que necesita un segundo mercado

Un script como este solo entra en largo mientras QQQ está por encima de su media de 50 días:

//@version=6
strategy("QQQ regime filter", overlay=true)
qqqUp = request.security("NASDAQ:QQQ", "D", close > ta.sma(close, 50))
fast = ta.sma(close, 20)
if ta.crossover(close, fast) and qqqUp
    strategy.entry("L", strategy.long)
if ta.crossunder(close, fast)
    strategy.close("L")

El problema es la línea 3. Su expresión, close > ta.sma(close, 50), se evalúa sobre las barras diarias del propio QQQ: los cierres de QQQ, la media de 50 días de QQQ. Por mucho que agregues las barras del gráfico, no vas a obtener eso. Hasta hace poco, PineForge rechazaba el script sin más. Ahora lo ejecuta, siempre que quien ejecute el motor le pase las barras de QQQ.

Qué necesita el motor

La cabecera C pública del motor tiene una llamada para ello (pineforge.h):

PF_API int strategy_set_symbol_feed(pf_strategy_t s, const char* key, const char* timeframe,
                                    const pf_bar_t* bars, const int64_t* close_ms, int n);

Léela como tres requisitos. El parámetro key es la cadena exacta que pasa el script, NASDAQ:QQQ, para que las barras se asocien a la llamada que las pidió. El timeframe tiene que ser el solicitado: una petición diaria necesita barras diarias, y un feed del mismo símbolo en otra temporalidad no sirve. Y close_ms da a cada barra su propia hora de cierre, porque «no se presupone ningún calendario para el símbolo»: el motor no sabe cuándo cierra el Nasdaq salvo que se lo digan las barras. Datos como la sesión, la divisa y el tamaño del tick del símbolo son opcionales y se pasan con una llamada hermana.

¿Qué reloj manda?

Las horas de cierre importan porque cada mercado cierra a una hora distinta. En cada barra del gráfico, el motor entrega a la petición la última barra del otro símbolo que haya cerrado como muy tarde al cierre de la barra del gráfico (o, con lookahead activado, la última que haya abierto como muy tarde a su apertura) (pineforge.h). Así, «una barra diaria que cierra después del cierre de sesión del gráfico se ve un día de gráfico más tarde, igual que la ve TradingView» (guía MTF).

Es una regla de TradingView, no un atajo de PineForge. Los tests del motor guardan una exportación de TradingView de un gráfico diario del oro que pide el índice del dólar. La barra diaria del oro que abre el domingo por la tarde cierra a las 17:00 ET del lunes; la barra del lunes del índice del dólar cierra a las 19:00 ET, dos horas después. Así que en TradingView esa barra del oro lee la barra del viernes del índice del dólar, y la del lunes llega con la siguiente barra del oro (notas del fixture). Un backtester que entrega a una barra del gráfico la barra del otro mercado antes de que esta haya cerrado está leyendo el futuro.

Cuando faltan las barras

El script se transpila igualmente, con un aviso de que la petición lee «las barras de otro símbolo» y de que, «si no hay ninguna instalada, la ejecución se detiene con un error en el punto donde se lee su valor» (codegen). En la primera lectura, la ejecución se detiene con un mensaje que nombra la llamada (código fuente):

request.security("NASDAQ:QQQ", "D", ...) at line 3: no data is pinned for this request, and its value was read

Lo que nunca hace es leer las barras del gráfico en lugar de las de QQQ (changelog). Un filtro calculado sobre el mercado equivocado produce una curva de equity verosímil que no significa nada, así que detenerse es la forma más segura de fallar. Hay dos casos en los que la ejecución no se detiene: una petición cuyo valor nunca se lee (por ejemplo, detrás de un input desactivado) y una cuyo valor solo llega a plots, alertas o tablas, que pasa a ser na con un aviso de que «las operaciones no se ven afectadas» (codegen).

Qué aportan la imagen Docker y los servidores MCP

Cada uno de ellos hace backtests sobre las barras de un solo mercado: el del gráfico.

  • La imagen Docker recibe strategy.pine y un único ohlcv.csv (README), y su harness no instala barras de ningún otro símbolo. El script de QQQ se detiene ahí con el mensaje de arriba, y la imagen termina con el código de salida 4.
  • El servidor MCP local está construido sobre esa imagen y hace el backtest de un único CSV. Su README lo dice claramente: «no instala barras de ningún otro símbolo … request.security sobre otro símbolo, por ejemplo, lo admite el motor, pero aquí una petición cuyo valor puede llegar a una operación detiene la ejecución» (README).
  • El servidor MCP alojado recibe un símbolo, un intervalo y un exchange, y descarga él mismo las barras de ese único mercado: solo cripto, así que una estrategia sobre acciones como el ejemplo de QQQ no puede ejecutarse ahí de ninguna manera (mismo README).

Aquí cae mucha gente en una trampa: la imagen Docker y el servidor local nunca le dicen al motor qué mercado es el del gráfico, así que un símbolo escrito como cadena cuenta como otro símbolo, aunque nombre el propio mercado del gráfico (notas de cobertura). Para el símbolo del gráfico, escribe syminfo.tickerid. Una petición de temporalidad superior sobre él no necesita datos adicionales, porque el motor construye esas barras a partir de las del gráfico (guía MTF). Hay un límite: en un gráfico intradía de una acción de EE. UU. o de un futuro del CME, la barra diaria de TradingView es la del propio mercado, con el cierre oficial, y eso no lo reproduce ninguna agregación (pineforge.h).

Qué significa para ti

  • ¿Filtras con el propio mercado del gráfico? Usa syminfo.tickerid y el script se ejecutará solo con tu CSV.
  • ¿Filtras con otro mercado? Ejecuta tú mismo el motor e instala las barras de ese mercado, con sus horas de cierre, antes de la ejecución. El harness de pruebas del repositorio del motor es un ejemplo completo: las instala a partir de un manifiesto de peticiones por script, cuyos campos define scripts/run_strategy.py.
  • ¿Comparas con TradingView? Con lookahead desactivado, que es el valor por defecto, cuenta con que la barra diaria de un mercado que cierra después que el tuyo llegue con un día de retraso. Es el comportamiento de TradingView, y el motor lo reproduce.

Comprobado el 2026-10-02 con el motor y el codegen de PineForge 1.0.1, las versiones publicadas ese día; este comportamiento llegó por primera vez en la 1.0.0.

Por dónde seguir