Une stratégie qui trade un marché mais filtre sur un autre a besoin des barres de ce second marché, et le CSV de votre graphique ne les contient pas. Ce dont le moteur de PineForge a besoin pour exécuter un tel script, ce qu’il fait quand ces barres manquent, et ce que fournissent l’image Docker et les serveurs MCP.
Mis à jour le 2026-10-04 : depuis la version 1.1.0, l’image pineforge-release installe les barres d’autres symboles pour
request.securityà partir de l’index JSON que désignePINEFORGE_SYMBOL_FEEDS, avec un flux par unité de temps demandée, sans agrégation (moteur #327, pineforge-release #23 ; voir le changelog 1.1.0 du moteur). Le reste de ce billet est tel que publié le 2026-10-02, pour la version 1.0.1.
Votre stratégie trade une action, mais n’achète que lorsque le Nasdaq est en tendance haussière. Sur TradingView, cela tient en une ligne, et le script tourne, parce que TradingView dispose des barres de tous les symboles. Un backtester qui s’appuie sur votre CSV n’a que celles du graphique. Demandez-lui QQQ : il n’a que trois options, refuser, s’arrêter, ou calculer en silence le filtre sur le mauvais marché.
PineForge choisit la deuxième, et seulement quand il n’a pas le choix : fournissez au moteur les barres de QQQ, et le script s’exécute.
La ligne qui réclame un second marché
Un script comme celui-ci ne prend de position longue que lorsque QQQ est au-dessus de sa moyenne sur 50 jours :
//@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")
Le problème, c’est la ligne 3. Son expression, close > ta.sma(close, 50), s’évalue sur les barres journalières de QQQ lui-même : les clôtures de QQQ, la moyenne sur 50 jours de QQQ. Aucune agrégation des barres du graphique, aussi poussée soit-elle, ne peut les produire. Jusqu’à récemment, PineForge refusait purement et simplement ce script. Il l’exécute désormais, à condition que celui qui fait tourner le moteur lui fournisse les barres de QQQ.
Ce dont le moteur a besoin
L’en-tête C public du moteur prévoit un appel pour cela (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);
On peut y lire trois exigences. La key est la chaîne exacte que passe le script, NASDAQ:QQQ, pour que les barres soient rattachées à l’appel qui les a demandées. Le timeframe doit être celui qui est demandé : une requête journalière exige des barres journalières, et un flux du même symbole dans une autre unité de temps ne compte pas. Enfin, close_ms donne à chaque barre sa propre heure de clôture, car « aucun calendrier n’est deviné pour le symbole » : le moteur ne sait pas quand le Nasdaq clôture tant que les barres ne le lui disent pas. Les informations comme la session du symbole, sa devise ou la taille de son tick sont facultatives, et passent par un appel voisin.
Quelle horloge fait foi
Les heures de clôture comptent, parce que deux marchés ne clôturent pas à la même heure. À chaque barre du graphique, le moteur fournit à la requête la dernière barre de l’autre symbole clôturée au plus tard à la clôture de la barre du graphique (ou, avec le lookahead activé, la dernière ouverte au plus tard à son ouverture) (pineforge.h). Ainsi, « une barre journalière qui clôture après la fin de séance du graphique est vue avec un jour de retard sur le graphique, comme la voit TradingView » (guide MTF).
C’est une règle de TradingView, pas un raccourci de PineForge. Les tests du moteur conservent l’export TradingView d’un graphique journalier de l’or qui interroge l’indice dollar. La barre journalière de l’or qui ouvre le dimanche soir clôture le lundi à 17:00 ET ; la barre du lundi de l’indice dollar clôture à 19:00 ET, deux heures plus tard. Sur TradingView, cette barre de l’or lit donc la barre du vendredi de l’indice dollar, et celle du lundi n’arrive qu’avec la barre de l’or suivante (notes des fixtures). Un backtester qui fournit à une barre du graphique la barre de l’autre marché avant que celle-ci n’ait clôturé lit dans l’avenir.
Quand les barres manquent
Le script se transpile quand même, avec un avertissement : la requête lit « les barres d’un autre symbole » et, « si aucune n’est installée, l’exécution s’arrête sur une erreur là où sa valeur est lue » (codegen). À la première lecture, l’exécution s’arrête avec un message qui nomme l’appel (source) :
request.security("NASDAQ:QQQ", "D", ...) at line 3: no data is pinned for this request, and its value was read
Ce qu’il ne fait jamais, en revanche, c’est lire les barres du graphique à la place de celles de QQQ (changelog). Un filtre calculé sur le mauvais marché produit une courbe d’equity plausible qui ne veut rien dire : s’arrêter reste donc la façon la plus sûre d’échouer. Deux cas n’entraînent pas d’arrêt : une requête dont la valeur n’est jamais lue (par exemple derrière un input désactivé), et une requête dont la valeur n’alimente que des plots, des alertes ou des tables, qui devient alors na, avec un avertissement précisant que « les trades ne sont pas affectés » (codegen).
Ce que fournissent l’image Docker et les serveurs MCP
Chacun d’eux backteste les barres d’un seul marché : celui du graphique.
- L’image Docker prend
strategy.pineet un seulohlcv.csv(README), et son harnais n’installe les barres d’aucun autre symbole. Le script QQQ s’y arrête avec le message ci-dessus, et l’image se termine avec le code de sortie 4. - Le serveur MCP local repose sur cette image et backteste un seul CSV. Son README le précise : il « n’installe les barres d’aucun autre symbole …
request.securitysur un autre symbole, par exemple, est pris en charge par le moteur, mais ici une requête dont la valeur peut atteindre un trade arrête l’exécution » (README). - Le serveur MCP hébergé prend un symbole, un intervalle et une plateforme d’échange, et récupère lui-même les barres de ce seul marché : crypto uniquement, si bien qu’une stratégie sur actions comme l’exemple QQQ ne peut pas du tout y tourner (même README).
Un piège guette ici : l’image Docker et le serveur local n’indiquent jamais au moteur quel est le marché du graphique ; un symbole écrit sous forme de chaîne compte donc comme un autre symbole, même quand il désigne le marché du graphique lui-même (notes de couverture). Pour le symbole du graphique, écrivez syminfo.tickerid. Une requête en unité de temps supérieure sur ce symbole ne demande alors aucune donnée supplémentaire, car le moteur construit ces barres à partir de celles du graphique (guide MTF). Une limite toutefois : sur un graphique intrajournalier d’une action américaine ou d’un future du CME, la barre journalière de TradingView est celle de la bourse elle-même, avec la clôture officielle, ce qu’aucune agrégation ne reproduit (pineforge.h).
Ce que cela change pour vous
- Vous filtrez sur le marché du graphique lui-même ? Utilisez
syminfo.tickerid, et le script tourne sur votre seul CSV. - Vous filtrez sur un autre marché ? Faites tourner le moteur vous-même et installez les barres de ce marché, avec leurs heures de clôture, avant l’exécution. Le harnais de test du dépôt du moteur en est un exemple concret : il les installe à partir d’un manifeste de requêtes propre à chaque script, dont les champs sont définis par
scripts/run_strategy.py. - Vous comparez avec TradingView ? Avec
lookaheaddésactivé, la valeur par défaut, attendez-vous à ce qu’une barre journalière d’un marché qui clôture après le vôtre arrive avec un jour de retard. C’est le comportement de TradingView, et le moteur s’y conforme.
Vérifié le 2026-10-02 avec le moteur et le codegen PineForge 1.0.1, sortis ce jour-là ; ce comportement est arrivé avec la 1.0.0.
Pour aller plus loin
- Backtester depuis Claude ou Cursor : les deux serveurs MCP, pas à pas.
- Faire tourner PineForge hors ligne : l’image Docker sur vos propres données.
- Le système de types de Pine v6, dans la pratique : y compris la façon dont la valeur par défaut du lookahead de
request.securitya changé d’une version de Pine à l’autre.