Ingeniería

Cuando dos motores Pine discrepan: validación cruzada PineForge vs PyneCore

Cada release pasa un barrido de paridad frente a TradingView y PyneCore. Así salen los números del último barrido y por qué un segundo motor «amigo» es la mejor herramienta de depuración que tenemos.

9 min de lectura#pinecore#pineforge#parity#engineering

Hay dos motores Pine «de verdad» fuera de TradingView que conocemos bien: el nuestro y PyneCore. PyneSys transpila Pine a Python con PyneComp; PyneCore es el runtime abierto que ejecuta el resultado. Otro lenguaje, otro runtime, mismo Pine en origen.

Usamos PyneCore como segundo oráculo. Cada release de PineForge pasa el barrido de paridad contra el CSV «List of Trades» de TradingView y contra la salida de PyneCore. Cuando los tres coinciden en los trades, nos creemos el resultado. Cuando dos sí y uno no, casi siempre el fallo está en el disidente — muchas veces en nosotros.

Aquí van los números del último barrido público.

El corpus

50 estrategias de referencia en el barrido que publicamos (el corpus grande de ~167 entradas es interno; este subconjunto es el que va al informe de comparación). Para cada una:

  • PyneComp → Python → PyneCore sobre el OHLCV canónico
  • PineForge codegen → C++ → pineforge-engine sobre el mismo OHLCV
  • Ambos se comparan con el export CSV de TradingView, recortado a
    [ventana OHLCV] ∩ [ventana entradas TV] ∩ [ventana entradas motor]
  • Grado de coincidencia en escala de 5 niveles (excelente / fuerte / moderada / débil / mínima)

El titular

De las 50, 47 comparten el mismo nivel en PineForge y PyneCore. Las dos máquinas marcan excelente (≥95 % de trades frente al CSV de TV) en 47; PineForge cae a fuerte en 2 de esas por cómo contabilizamos deltas de código frente al TV export — detalle de contabilidad, no de ejecución.

Las 3 que discrepan por nivel son curiosas: en todas PineForge queda por encima.

StrategyPineForgePyneCoreNotas
49-partial-exit-qty-percent🟢 excellent🟠 weakPyneCore emite demasiados fills de salida
06-liquidity-sweep🟢 excellent🟡 moderateDeriva de timing de salida en Pyne
07-scalping-strategy🟢 excellent🟡 moderatePnL p90 alto en Pyne; entradas OK

El caso gordo

49-partial-exit-qty-percent es el abismo. La estrategia usa strategy.exit(qty_percent=...) para ir deshaciendo trozos de posición en profit-take en bucle.

TV trades (raw, in-window):  725
PineForge engine trades:     852  →  725 in-window  →  725 matched (100% of TV)
PyneCore  engine trades:    3297  →  2805 in-window →  582 matched (80.3% of TV)

PyneCore dispara ~74 % más trades que TV en recuento bruto; PineForge queda en 0 % de delta de conteo tras alinear ventanas.

PineForge: count_delta 0.0000% · entry p90 0.0000% · exit p90 0.0004% · PnL p90 0.1321%
PyneCore : count_delta 74.1533% · entry p90 0.0000% · exit p90 1.0376% · PnL p90 (high)

Las entradas cuadran al pixel (p90 0 % en ambos). La pelea es de salidas: PyneCore parece re-disparar ciclos de salida parcial donde TV ya dio por cerrado el mandato de qty_percent.

No es un veredicto moral sobre PyneCore: es un vértice feo de la semántica de strategy.exit(), y nosotros lo molimos hasta cuadrar con TV tras verlo en el barrido. El juego del bug va en ambos sentidos — más de una vez PyneCore y TV coincidieron cuando nosotros no.

Las otras dos

06-liquidity-sweep y 07-scalping-strategy son la misma forma en menor magnitud: entradas alineadas al 100 %, salidas con deriva ~1–2 puntos porcentuales en PyneCore frente a <0.05 % en PineForge. Trails y fills parciales son donde la referencia de Pine está menos documentada; todo motor tiene que inferir comportamiento observable.

API de compilación PyneSys

PyneSys expone la compilación por HTTP:

curl -X POST https://api.pynesys.io/compiler/compile \
  -H "Authorization: Bearer pyne_..." \
  --data-urlencode 'script=//@version=6
strategy("ma cross", overlay=true)
if ta.crossover(close, ta.sma(close, 20))
    strategy.entry("L", strategy.long)'

La respuesta es Python pensado para pynecore:

"""
@pyne
 
This code was compiled by PyneComp v6.0.31 — the Pine Script to Python compiler.
Run with open-source PyneCore: https://pynecore.org
Compile Pine Scripts online at PyneSys: https://pynesys.io
"""
from pynecore.lib import close, script, strategy, ta
 
 
@script.strategy("ma cross", overlay=True)
def main():
    if ta.crossover(close, ta.sma(close, 20)):
        strategy.entry('L', strategy.long)
 
 
if __name__ == "__main__":
    from pynecore.standalone import run
    run(__file__)

Lo guardas, pip install pynecore, apuntas al mismo CSV OHLCV y tienes segunda salida para comparar con PineForge.

Para qué sirve

Un solo motor que dice «12,4 % en esta ventana» solo afirma eso: que él lo cree. Sin contraste, te lo tragas o no.

Dos motores independientes con el mismo script y mismos datos es evidencia fuerte. Si discrepan, la discrepancia es hipótesis: bug en uno, ambigüedad semántica, o datos con arista que nadie contempló. Las tres cosas importan antes de arriesgar pasta real.

Lo que este post no cubre

  • Deriva a nivel indicador. Otro informe mide acuerdo indicador a indicador entre PineForge, PyneCore y TV; vive en benchmarks/results/indicator_comparison.md del repo del motor.
  • Las ~110 estrategias del corpus interno que no están en el barrido público. Ahí viven UDT, request.security en serie, grupos OCA… Cuando publiquemos metodología completa, otro artículo.

Hazlo tú

Dos motores, un fuente, tres lecturas. Si todas cuadran, adelante. Si no, mejor enterarte antes que mover capital.