PagesCycle de vie d'une stratégie · 8 / 29
Cycle de vie de la stratégie
Une stratégie QuantCraft est un fichier Python ordinaire qui définit un petit ensemble de fonctions de callback. Le moteur de backtest appelle ces fonctions dans un ordre fixe pendant qu'il parcourt les données historiques. Vous ne les appelez pas vous-même — c'est le moteur qui le fait. Utilisez Test pour les piloter (exécution du code).
Le cycle de vie, du début à la fin :
on_init → on_bar → on_tick (×4 per bar) → account tick(close) → on_finish
└─────────── repeats for every bar ───────────┘En backtest, on_tick se déclenche quatre fois par barre sur un OHLC synthétique issu de chaque barre (sans véritable historique de ticks). Après ces quatre ticks, le moteur effectue une dernière valorisation du compte à la clôture — pas un cinquième on_tick. on_timer n'est disponible que sur les exécutions forward (forward sur graphique, forward en masse / QuantCloud) lorsque timerIntervalMs est défini — la fenêtre Run backtest de l'IDE n'a pas de commande de minuteur. Sur les exécutions forward, on_tick s'exécute à chaque mise à jour de prix du courtier connecté ; aux frontières intrajournalières, on_tick puis on_bar se déclenchent souvent — ne supposez pas l'ordre du backtest.
Cette page présente chaque callback, la façon dont les stratégies multi-symboles s'y raccordent, et les bases nécessaires pour écrire votre première stratégie.
Le fichier de stratégie
Une stratégie minimale ressemble à ceci :
import quantcraft.process.runtime as bt # account, chart_indicators, qc_* handles
from quantcraft import Timeframe
import quantcraft.runtime as rt
def on_init(symbols=None):
pass
def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None):
close_px = float(bar["close"])
daily = rt.get_bar(Timeframe.DAILY, shift=1)
def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None):
if tick_in_bar == 3:
close_px = price
def on_finish(bars, symbol=None, bars_by_symbol=None):
passQuelques règles s'appliquent à tous les callbacks :
- Ils doivent être des fonctions de niveau supérieur dans votre fichier (pas imbriquées ni dans une classe).
- Le moteur de backtest exige des
on_init,on_bar,on_ticketon_finishappelables (sinon l'exécution échoue avecUser script must define callable: …). Les boutons Test, Compile et Debug Test de l'IDE apparaissent lorsque l'onglet.pyactif définiton_init/on_bar/on_finish; unon_tickmanquant fait tout de même échouer le démarrage du backtest.on_timerest optionnel et n'est utilisé que sur les exécutions forward avec un intervalle de minuteur. - N'ajoutez que les arguments optionnels que vous utilisez.
calendar,symbols(suron_init) et les arguments deon_finishsont associés par nom ;fundamentalsetsymbolsont associés en essayant tour à tour les formes courantes, donc gardez-les dans l'ordre documenté. La signature complète la plus sûre estdef on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None). Uncalendardéclaré reçoit toujours le handle du calendrier ouNone— jamais l'objet de données fondamentales. - Dans les callbacks, vous passez des ordres via
accountet enregistrez les valeurs d'indicateurs viachart_indicators(séries d'indicateurs) — tous deux lus depuisquantcraft.process.runtime.
Globales du runtime
Le moteur expose ces éléments depuis quantcraft.process.runtime avant l'exécution des callbacks :
| Nom | Rôle |
|---|---|
account | Instance de PaperAccount configurée depuis l'onglet Simulation (solde + commission / slippage). Sur les exécutions forward, c'est un compte de courtier réel avec la même API qui passe de vrais ordres — voir Modèle de compte |
chart_indicators | Collecteur ChartIndicatorSeries fusionné dans le résultat — collectez les valeurs au fil du parcours des barres, émettez les séries dans on_finish |
qc_fundamentals | Handle QcFundamentals optionnel lorsque les données fondamentales sont chargées |
qc_fama_french | QcFamaFrench optionnel lorsque les facteurs Fama-French globaux sont chargés ; préférez fama_french.qc dans les callbacks — voir Facteurs Fama-French |
qc_calendar | BacktestCalendar | None — données de calendrier lorsque Include calendar data est activé ; couvre tous les symboles tradés. Backtests uniquement — toujours None sur les exécutions forward. Les callbacks peuvent recevoir le même handle en nommant un argument calendar — voir Données de calendrier |
qc_ohlcv_by_tf | Tampons optionnels d'intervalles secondaires indexés par chaîne d'intervalle (avancé ; préférez get_bar) |
get_bar(tf, shift=1) | API recommandée pour l'intervalle principal et les intervalles supplémentaires — renvoie un BarSnapshot ou None ; à importer via import quantcraft.runtime as rt |
Préférez l'argument bars de on_finish(bars, ...) pour les calculs en série complète — ne vous appuyez pas sur le qc_ohlcv de niveau module dans les stratégies de l'IDE (il est souvent None à l'import, vaut None lors d'une exécution ciblant un screener, et couvre sinon toute la plage chargée préchauffage compris). Lisez les prix depuis l'objet bar dans les callbacks. isinstance(bar, dict) est False — OhlcvBarView est un Mapping (l'accès de type dict fonctionne toujours).
Lisez-les comme des attributs de module. Chaque handle repose sur une valeur propre à la session, donc
bt.account/bt.chart_indicatorsse rapportent toujours à cette exécution.from quantcraft.process.runtime import accountlie un instantané — acceptable dans le fichier d'algo racine en backtest, car le moteur relie ces noms après l'avoir chargé (les exécutions forward ne relient queaccount,qc_fundamentalsetqc_fama_french; utilisez doncbt.chart_indicatorsetrt.qc_ohlcv*dans ce cas), mais incorrect dans un module d'aide séparé, qui reste en cache danssys.moduleset conserverait le handle de la première session. Voir Poignées de module et noms importés.
Intervalles secondaires
L'intervalle principal de l'exécution est toujours disponible via rt.get_bar. Sélectionnez des tailles de barre supplémentaires dans la liste Additional timeframes de la fenêtre Run backtest, onglet Symbol & data (Backtests), puis lisez-les depuis n'importe quel callback :
from quantcraft import Timeframe
import quantcraft.runtime as rt
def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None):
daily = rt.get_bar(Timeframe.DAILY, shift=1)
if daily and bar.close[0] > daily.close:
account.open_trade(...)La sémantique complète de shift, l'énumération Timeframe et les réserves propres aux exécutions forward figurent dans Données OHLCV et de barres.
Moment d'exécution des ordres
bar["close"]danson_barest la clôture de la barre d'exécution courante.account.open_trade/close_tradeprennent un prix demandé, puis appliquent les modèles de slippage et de commission de l'exécution. Lesentry_price/exit_pricestockés sont les prix d'exécution ; les prix demandés sont conservés dansraw_entry_price/raw_exit_price. Avec le slippage par part du volume par défaut, les exécutions diffèrent généralement du prix que vous transmettez.- Pour viser la clôture de la barre depuis
on_bar, transmettezfloat(bar["close"])(toujours soumis au slippage/à la commission). - Pour exécuter pendant la simulation intrabarre, appelez depuis
on_ticklorsquetick_in_bar == 3—priceest alors la clôture de ce symbole pour la ligne.
on_init — tout préparer
S'exécute juste après que le moteur a préparé le compte, chargé les données de marché et construit le runtime — mais avant le traitement de toute barre.
Utilisez-la pour :
- Initialiser les variables que votre stratégie conserve d'une barre à l'autre (compteurs, machines à états, valeurs de paramètres).
- Configurer des indicateurs ou des tampons de préchauffage.
- Lire les paramètres d'entrée afin qu'ils pilotent le reste de l'exécution.
def on_init(symbols=None):
global lookback, position_size
lookback = 20
position_size = 100Vous n'avez pas accès à une barre courante dans on_init — la simulation n'a pas encore commencé.
Paramètre symbols (exécutions avec screener uniquement) : Lorsque le backtest utilise une source de symboles Screener, on_init est de nouveau déclenchée à chaque rééquilibrage avec symbols=<liste de tickers> pour le nouvel univers. Cela permet à votre stratégie de réagir à un nouvel ensemble de tickers sans redémarrer la simulation.
- Au démarrage de l'exécution,
on_initse déclenche deux fois de suite — une fois avecsymbols=None(l'appel d'initialisation standard) et une fois avecsymbols=<tickers initiaux>. - Les nouveaux déclenchements ne réinitialisent pas l'état de niveau module ; toutes les variables définies lors du premier appel sont toujours là.
- Les stratégies qui définissent
def on_init():(sans paramètre) fonctionnent exactement comme avant — le moteur associe les arguments par nom, donc le paramètre manquant est simplement ignoré.
on_bar — une fois par barre
S'exécute une fois par barre d'exécution, avant les quatre ticks intrabarre de cette barre. C'est ici que la plupart des stratégies prennent leurs décisions.
def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None):
if bar.bars_back < 1:
return
if bar.close[0] > bar.close[1]:
account.open_trade(...)Paramètres :
bar_index— indice, base zéro, de la barre d'exécution courante.bar— la vue de la barre courante (OhlcvBarView). Se comporte comme un mapping en lecture seule (bar["open"],bar["high"],bar["low"],bar["close"],bar["volume"],bar["t"]) et expose aussi l'historique par décalages :bar.close[0]est la clôture de la barre courante,bar.close[1]celle de la barre précédente, et ainsi de suite.len(bar.close)etbar.bars_backindiquent la quantité d'historique disponible. Ce n'est pas undictordinaire (isinstance(bar, dict)estFalse).fundamentals— instantané des données fondamentales pour cette barre, ouNones'il n'est pas configuré.calendar— handleBacktestCalendarlorsque Include calendar data est activé, sinonNone(toujoursNonesur les exécutions forward). Voir Données de calendrier pour l'API complète. Les arguments étant associés par nom, omettre ce paramètre laisse votre callback inchangé.symbol— chaîne du ticker en majuscules. Utile dans les stratégies multi-symboles ; vous pouvez l'omettre si vous ne chargez qu'un seul symbole.
Schémas courants :
- Décider des entrées/sorties à la clôture de la barre : transmettez
bar.close[0](oufloat(bar["close"])) àaccount.open_trade/account.close_trade. - Calculer des indicateurs simples en lisant des décalages comme
bar.close[0],bar.close[1], … - Ignorer les premières barres qui n'ont pas assez d'historique (
if bar.bars_back < N: return).
Astuce :
on_bars'exécute toujours avant les callbacks de tick de la même barre.
on_tick — mises à jour de prix intrabarre et en direct
on_tick a la même signature en backtest et en forward, mais le moment où elle se déclenche et la signification de price dépendent du mode d'exécution.
Backtest (Test)
S'exécute jusqu'à quatre fois par barre, après on_bar, pour simuler le mouvement à l'intérieur de la barre. Le moteur parcourt l'OHLC de chaque barre dans cet ordre :
tick_in_bar | Signification |
|---|---|
0 | Ouverture de la barre |
1 | Plus haut de la barre |
2 | Plus bas de la barre |
3 | Clôture de la barre |
Pas de vraies données de ticks en backtest. Les exécutions historiques ne chargent pas l'historique de ticks du courtier. Les quatre appels par barre utilisent des prix synthétiques dérivés du seul OHLC de chaque barre. Vous pouvez approximer des exécutions intrabarre en simulation, mais le comportement de
on_ticken backtest ne correspondra pas aux ticks forward réels. Pour des décisions à la clôture de la barre,on_barsuffit —on_tickavectick_in_bar == 3équivaut à agir sur la clôture de la barre en simulation.
def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None):
if tick_in_bar == 3:
# Backtest simulation: closing tick fill
account.close_trade(position_id, price)Utilisez on_tick en backtest lorsque le moment dans la barre compte en simulation — par exemple pour exécuter au tick de clôture, simuler un stop intrabarre ou évaluer le toucher du plus haut ou du plus bas de la barre.
Exécutions forward (forward sur graphique, forward en masse / QuantCloud)
Sur les exécutions forward, on_tick s'exécute chaque fois que la plateforme reçoit un prix de votre courtier connecté tant que votre algo est attaché. Il n'y a pas de parcours OHLC fixe en quatre ticks — chaque appel reflète une cotation en direct.
def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None):
# Forward run: react to each broker price update
if price <= stop_level:
account.close_trade(position_id, price)price— la dernière cotation du courtier pour ce symbole.bar— la vue de la barre courante pour l'historique (même forme qu'en backtest).tick_in_bar— peut rester présent pour la compatibilité de signature ; sur les exécutions forward, raisonnez surprice, pas sur des phases OHLC synthétiques.
Utilisez on_tick en forward pour la logique intrabarre en direct — stops, sorties suiveuses ou toute réaction aux valorisations entre les clôtures de barres.
Paramètres (les deux modes)
bar_index— identique àon_bar.tick_in_bar— en backtest,0,1,2ou3(étape OHLC synthétique). Sur les exécutions forward, ne vous y fiez pas pour une logique de phase de barre.price— étape OHLC synthétique en backtest ; cotation du courtier en direct sur les exécutions forward.bar— la même vue de barre que danson_bar.fundamentals,calendar,symbol— même sens que danson_bar.
Si vous n'avez besoin que de décisions à la clôture de la barre en backtest, vous pouvez ignorer entièrement
on_ticket placer votre logique danson_bar.
on_timer — callback périodique optionnel (exécutions forward uniquement)
Backtest de l'IDE : la fenêtre Run backtest ne définit pas d'intervalle de minuteur. Même si vous définissez
on_timerdans votre fichier de stratégie, elle n'est pas appelée pendant les backtests de l'IDE. Placez la logique périodique ou temporelle danson_barouon_tickpour les backtests.
Les exécutions forward (test forward sur graphique, forward en masse / QuantCloud) peuvent définir timerIntervalMs dans leur fenêtre d'exécution. Lorsqu'il est configuré, on_timer s'exécute à intervalle de temps fixe entre les ticks, uniquement si deux conditions sont réunies :
- Vous avez configuré
timer_interval_ms/timerIntervalMspour l'exécution (un entier positif en millisecondes). - Votre fichier définit une fonction
on_timer.
def on_timer(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None):
passLa signature a la même forme que on_tick. Utilisez on_timer pour des comportements temporels (par exemple : rafraîchir un signal lent chaque minute, ou planifier une vérification indépendante du nombre de barres). Si timer_interval_ms n'est pas défini, on_timer n'est jamais appelée même si elle est définie.
Données de calendrier dans les callbacks
Lorsque Include calendar data est activé dans l'onglet Symbol & data, n'importe quel callback peut recevoir un handle BacktestCalendar en ajoutant un paramètre calendar :
def on_bar(bar_index, bar, calendar=None, symbol=None):
if calendar is None: # always guard — not every run enables calendar data
return
days = calendar.days_to_earnings(bar["t"], symbol)
if days is not None and days <= 2:
return # avoid trading into an earnings print
divs = calendar.dividends(bar["t"], symbol)
if divs and (divs[-1].amount or 0) > 0.5: # amount can be None
...Le même handle est disponible dans on_tick et on_timer — mais les données de calendrier sont réservées au backtest ; sur les exécutions forward, calendar vaut donc toujours None. Toutes les recherches sont point-in-time — dividends(bar["t"], symbol) ne renvoie que les dividendes qui étaient publics à cette barre, jamais les futurs.
Méthodes principales :
| Méthode | Renvoie |
|---|---|
dividends(bar_time, symbol=None) | Dividendes publics à cette barre (du plus ancien au plus récent) |
splits(bar_time, symbol=None) | Fractionnements annoncés à cette barre (du plus ancien au plus récent) |
next_earnings(bar_time, symbol=None) | {date, basis, estimated, …} ou None |
days_to_earnings(bar_time, symbol=None) | int ou None |
has(symbol=None) | Indique si ce symbole a des données de calendrier |
Les listes vont du plus ancien au plus récent — [-1] est l'événement le plus récent. C'est l'inverse des données fondamentales, où l'indice 0 correspond à la période la plus récente.
La globale du runtime qc_calendar contient le même handle si vous préférez le lire depuis quantcraft.process.runtime plutôt que depuis l'argument du callback.
Voir Données de calendrier pour les schémas de champs d'événements, les détails de simulation des opérations sur titres et le dépannage.
on_finish — conclure
S'exécute une seule fois, après la dernière barre. Utilisez-la pour calculer les sorties finales, construire des indicateurs de graphique ou afficher un résumé.
def on_finish(bars, symbol=None, bars_by_symbol=None):
closes = [b["close"] for b in bars]
bt.chart_indicators.add(...)Paramètres :
bars— liste chronologique de dictionnaires OHLC pour le symbole d'horloge, du plus ancien au plus récent — la fenêtre exécutée, les mêmes lignes que dessine le graphique de Test Results. Chaque entrée contientt,open,high,low,close,volumeet, éventuellement,fundamentals. C'est la série canonique sur tout l'historique pouron_finish.symbol— ticker en majuscules du symbole d'horloge.- Dans une exécution Screener, il n'y a pas de symbole d'horloge :
barsvaut[]etsymbolvaut""; utilisez doncbars_by_symbol. bars_by_symbol— pour les exécutions multi-symboles, un dict associant chaque ticker à sa liste chronologique de barres (alignée sur l'horloge d'exécution). Pour les exécutions à un seul symbole, vous pouvez l'ignorer.
Usages courants :
- Transmettre les séries d'indicateurs à
chart_indicators.add(...)pour qu'elles apparaissent sur les graphiques de résultats. Préférez les valeurs que vous avez accumulées par symbole pendant le parcours des barres ; ne recalculez à partir debarsque les indicateurs que vous n'avez pas suivis barre par barre — voir Séries d'indicateurs. - Afficher ou journaliser un résumé final dans le Panneau de sortie.
- Actualiser les métriques du portefeuille pour la charge utile du résultat.
Chaque
returndanson_finishest silencieux. L'exécution réussit quand même et Test Results s'affiche toujours — un retour anticipé signifie simplement que vos séries ou métriques n'ont jamais été jointes, sans aucune erreur. Affichez verssys.stderrà chaque garde pendant le débogage ; le message apparaît dans le Panneau de sortie.
Bases du multi-symboles
Les stratégies QuantCraft peuvent s'exécuter sur un symbole ou sur plusieurs à la fois. Le cycle de vie reste le même — seules les signatures des callbacks et les données qu'ils reçoivent changent légèrement.
Configurer plusieurs symboles
Dans l'onglet Symbol & data de Run backtest, sélectionnez un ou plusieurs tickers en majuscules. Tous les symboles listés sont chargés et simulés ensemble.
Le symbole d'horloge
Lorsque vous exécutez plusieurs symboles, l'un d'eux pilote l'horloge — ses horodatages de barres décident du moment où les callbacks s'exécutent. La fenêtre Run backtest n'a aucune commande de symbole principal. Ordre de résolution :
- Un
SYMBOL = "AAPL"de niveau supérieur dans votre fichier de stratégie, si ce ticker figure dans la liste chargée. - Sinon, le premier symbole chargé.
Avec une source de symboles Screener, il n'y a aucun symbole d'horloge — l'ensemble de symboles change au fil de l'exécution. rt.get_bar lit toujours les barres du symbole d'horloge (et renvoie None sur une exécution Screener) ; pour l'historique d'un autre ticker, utilisez donc les décalages bar.close[i] de ce ticker.
Le symbole d'horloge est aussi celui que on_finish(bars, symbol=...) utilise pour ses arguments bars et symbol.
Callbacks par symbole
Pour chaque barre d'exécution, le moteur parcourt les symboles dans l'ordre de la liste :
on_bars'exécute une fois par ticker chargé à l'heure de cette barre (avant tout tick de cette barre).- Backtest uniquement : pour chaque
tick_in_bar(0= ouverture,1= plus haut,2= plus bas,3= clôture),on_ticks'exécute une fois par ticker sur un OHLC synthétique (pas de vraies données de ticks). - Backtest uniquement : après les quatre tours de ticks, un
tick(close_map)final du compte valorise le capital à la clôture — pas un cinquièmeon_tick. - Exécutions forward :
on_ticks'exécute à chaque mise à jour de prix du courtier pour chaque symbole à mesure que les cotations arrivent de votre courtier connecté (sans se limiter à quatre appels par barre). Aux frontières intrajournalières,on_tickpuison_barse déclenchent souvent — ne supposez pas l'ordre du backtest. on_timer(uniquement sur les exécutions forward avectimerIntervalMsconfiguré — pas en backtest de l'IDE) suit le même schéma par ticker entre les ticks.
Cela signifie qu'un on_bar multi-symboles est invoqué, par exemple, trois fois par barre si vous avez trois symboles chargés. Utilisez le paramètre symbol pour savoir pour quel ticker l'appel est fait :
def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None):
if symbol == "AAPL":
...
elif symbol == "MSFT":
...La vue bar appartient toujours au symbole pour lequel le callback a été invoqué. Il en va de même pour price dans on_tick.
Alignement « as-of ». Pour chaque heure de barre d'exécution, chaque ticker reçoit sa dernière barre à cette heure ou avant. Un ticker sans barre à cette heure précise (arrêt de cotation, minute manquante, calendrier de négociation différent) reçoit de nouveau sa barre précédente. Si vous comptez des barres ou des signaux par ticker, ignorez les répétitions en mémorisant le dernier bar["t"] vu pour ce symbole :
_last_t = {}
def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None):
if _last_t.get(symbol) == bar["t"]:
return # same bar as last step for this ticker
_last_t[symbol] = bar["t"]
...on_finish en multi-symboles
on_finish ne s'exécute toujours qu'une fois, à la fin. Pour travailler avec l'historique de tous les symboles, utilisez bars_by_symbol :
def on_finish(bars, symbol=None, bars_by_symbol=None):
if bars_by_symbol:
for ticker, ticker_bars in bars_by_symbol.items():
...Mise en pratique
Une stratégie typique à un seul symbole :
- Définit des paramètres d'entrée ou un autre état dans
on_init. - Lit l'historique des prix (OHLCV) via
bar.close[0],bar.close[1], … danson_barpour prendre ses décisions de trading, et appelleaccount.open_trade/account.close_trade. - Utilise éventuellement
on_tick— en backtest, pour des exécutions intrabarre synthétiques (p. ex.tick_in_bar == 3pour le tick de clôture) ; sur les exécutions forward, pour réagir aux prix du courtier en direct entre les clôtures de barres. - Utilise éventuellement
on_timersur les exécutions forward lorsquetimerIntervalMsest configuré (indisponible dans Run backtest de l'IDE). - Construit les indicateurs de graphique et finalise les résultats dans
on_finishà l'aide debars.
Une stratégie multi-symboles utilise les mêmes callbacks, se contente de se brancher sur l'argument symbol à l'intérieur et lit les données par ticker via la bar qu'elle reçoit — et via bars_by_symbol dans on_finish.
Besoin de plus de détails — voir Modèle de compte, Données fondamentales, Facteurs Fama-French, Données OHLCV et de barres, Séries d'indicateurs, ou l'onglet Documentation intégré dans la barre latérale droite (disposition de l'espace de travail).

