Arnaud Barbier
Moteur de backtestMode Algo · octobre 2026

Mode Algo, du signal aux trades gérés, avec frais et sizing

Le mode Algo transforme un signal en stratégie complète. L'utilisateur choisit ses entrées, décrit la gestion de chaque position et configure les frais et la taille des positions ; le moteur, compilé avec Numba, exécute le backtest et restitue chaque trade avec son prix visé, son prix exécuté et la raison de sa sortie.

Deux stratégies au même signal peuvent diverger fortement par la seule gestion de leurs positions, et c'est la partie que l'on teste le moins. Le mode Algo permet donc de décrire cette gestion à quatre niveaux, du simple réglage d'un TP et d'un SL jusqu'à une fonction compilée, et de la tester à part : on garde les entrées et on ne change que la sortie, ou l'on tire les entrées au hasard en gardant la même logique de sortie. Le sizing, lui, peut être modifié sans relancer le backtest. Le signal peut être étudié en amont dans le mode Research.

4niveaux pour décrire la gestion des positions
17codes de sortie, chacun tracé dans le journal des trades
2 714trades redimensionnés depuis la tape, sans relancer le noyau
1 %de l'equity risqué par trade dans l'exemple de sizing
Aperçu
Déclaration de deux profils de sortie, trend_profile et fast_profile
Les profils. Deux profils de sortie, l'un pour suivre la tendance, l'autre plus rapide. Ils sont compilés une fois, puis attribués par setup ou par régime. Cliquez sur une capture pour l'agrandir.
Backtest EMA 50/100 avec régime H1 affiché dans le graphique Research
Les trades. Le backtest affiché dans le graphique du mode Research : régimes H1 en zones colorées, entrées en triangles, sorties en croix.
Tableau metrics trades_df : dates, prix d'entrée et de sortie, rendement brut et net, commission
Le journal. metrics["trades_df"] : une ligne par sortie, avec les prix d'entrée et de sortie, le rendement brut et net, la commission et le spread.
Courbe d'equity en dollars et risque par trade constant à 1 %
Le sizing. La même tape de trades redimensionnée à 1 % de risque par trade : courbe d'equity en dollars et risque engagé à chaque trade.
01Du signal au trade

Ce que l'utilisateur décrit et ce que le moteur prend en charge

Le mode Algo prend en charge tout le backtest d'une stratégie sur un actif. L'utilisateur décrit sa logique : indicateurs, conditions d'entrée et règles de gestion. Le moteur applique le modèle d'exécution, puis renvoie les rendements, les métriques et le journal des trades.

  1. 01Features

    Indicateurs et données d'unités de temps supérieures, alignés sur la série principale.

  2. 02Setups

    Scores long et short, choix d'une entrée et prise en compte du régime.

  3. 03Exécution

    Timing, profils de sortie, règles de gestion et frais.

  4. 04Résultat

    Journal des trades, tape pour le sizing, graphique et tests de validation.

Le parcours d'un backtest mono-stratégie. Les modes allocation et investissement sont présentés sur leurs propres pages.

Choisir entre plusieurs setups

Chaque setup produit cinq colonnes : scores long et short, états actifs et identifiant. DecisionConfig retient le meilleur score et tranche les égalités selon la règle choisie. L'identifiant du setup suit ensuite le trade et peut désigner son profil de sortie.

S'adapter au régime de marché

Un régime peut bloquer ou pondérer une entrée, puis changer son profil ou sa stratégie de sortie. Il est fourni sous forme d'identifiants bruts : le moteur applique lui-même la disponibilité et le délai d'action, sans décalage manuel.

02Indicateurs et causalité

Utiliser une donnée H1 seulement après la clôture de sa bougie

Une feature est une fonction qui lit les colonnes du moteur, par exemple f.col("Close"), et renvoie une ou plusieurs séries. Les colonnes calculées rejoignent ensuite le moteur, et les règles de sortie peuvent les utiliser par leur nom.

10:00 → 11:00Bougie H1 en formation

Sa valeur finale ne peut pas servir aux décisions M15 prises avant sa clôture.

À partir de 11:00Valeur H1 disponible

bar_close aligne la valeur au bon moment ; max_age écarte une valeur trop ancienne.

feature.add(
    my_indicator, "h1_trend",
    on_ohlcv_matrix="algo_h1", matrix_asset="XAUUSD",
    align_to="main", availability="bar_close",
    source_tf="H1", max_age="2h",
)
Déclaration d'une feature H1 sur une série M15. Un simple forward-fill depuis 10:00 donnerait la valeur finale une heure trop tôt.

BacktestConfig fixe ensuite le timing, les sessions, les filtres de bougies, le nombre maximal d'entrées, le cooldown et les sorties par défaut. Avec entry_delay=1, l'entrée se fait à l'ouverture de la bougie suivante ; une exécution sur la bougie du signal serait souvent trop optimiste.

03Quatre niveaux de gestion

Décrire la gestion des positions, du réglage simple à la fonction compilée

Deux stratégies qui partagent le même signal peuvent obtenir des résultats très différents selon la façon dont leurs positions sont gérées. Le moteur permet de décrire cette gestion à quatre niveaux, chacun plus précis que le précédent :

NiveauCe que l'on déclareCoût d'une modification
01 · ConfigurationTP, SL, break-even, trailing, durée maximale et passage à plat, dans BacktestConfig.Aucune recompilation
02 · ProfilsUn ExitProfileSpec nommé, choisi par setup ou par régime. Les champs non renseignés reprennent la configuration.Aucune recompilation
03 · Phases et règlesDes PhaseSpec contenant des PositionRule : déclencheurs, sorties partielles, renforts, déplacement du stop.Aucune recompilation
04 · Stratégie njitUne fonction personnalisée via ExitStrategySpec, avec un état local ou global.Recompilation lorsque le code change
Code : deux ExitProfileSpec, trend_profile et fast_profile, et leur correspondance avec un identifiant
Les deux profils de l'exemple EMA : trend_profile (TP 1 %, SL 0,1 %) et fast_profile (TP 0,4 %, SL 0,1 %). Le setup 0 utilise trend_profile par défaut ; le régime peut le remplacer.

Avec les phases, une position commence en phase 0. Une règle peut se déclencher sur une excursion, un multiple du risque, un nombre de barres, une feature ou une combinaison de conditions. Elle peut alors prendre un bénéfice partiel, déplacer le stop, changer l'objectif, renforcer la position ou passer à la phase suivante.

  1. 01Entrer

    Le setup choisit le profil et le risque initial.

  2. 02Déclencher

    Une règle de la phase en cours observe sa condition.

  3. 03Agir

    Prendre une partie, protéger le stop ou renforcer.

  4. 04Changer de phase

    Appliquer les règles prévues pour la suite du trade.

Exemple de logique déclarative.

PartialConfig, PyramidConfig et AveragingConfig décrivent respectivement les sorties échelonnées, les ajouts aux positions gagnantes et les ajouts aux positions perdantes. Un état global peut aussi limiter le nombre de positions simultanées ou imposer une pause après plusieurs stops consécutifs.

04Frais et sizing

Passer du prix théorique au prix exécuté, puis à la taille de position

Appliquer les frais

ExecutionCostSpec décrit le contrat, la taille des lots, le spread, les commissions et le slippage. En mono-actif, ces paramètres se déclarent à la racine de la spec.

Le slippage est toujours défavorable et dépend de l'amplitude de la bougie précédente, avec une pénalité plus forte sur les stops. Si le prix ouvre au-delà du stop, la sortie se fait au prix d'ouverture.

Dimensionner sans relancer le backtest

Avec return_tape=True, le run produit une tape de trades. engine.run_sizing(...) recalcule leurs tailles sans relancer le noyau : on peut ainsi comparer plusieurs règles de sizing en quelques secondes.

Une modification des entrées ou de leur gestion demande en revanche un nouveau backtest.

execution_costs = ExecutionCostSpec(
    enabled=True, product_type="cfd",
    contract_spec=ContractSpec(instrument_id="XAUUSD",
                               contract_size=100.0, quote_currency="USD"),
    spread_points=0.02,                      # écart de prix, pas un %
    commission_per_lot_round_turn_usd=3.0,   # entrée + sortie
    slippage=SlippageSpec(enabled=True, adverse_only=True,
                          model="simple_range", simple_range_frac=0.0025,
                          stop_market_mult=1.25),
)

rets, metrics, tape = engine.run(..., execution_costs=execution_costs,
                                 return_tape=True)
sized = engine.run_sizing(tape=tape, sizing=sizing,
                          execution_costs=execution_costs,
                          strict_tape_context=True)
Une déclaration de frais pour l'or, puis le sizing appliqué à la tape du run. Le contexte strict vérifie que la tape correspond bien au run.
Courbe d'equity en dollars de juillet 2023 à 2026 et risque par trade constant à 1 %
La tape de l'exemple EMA redimensionnée à 1 % de risque par trade. En bas, le risque engagé reste constant sur toute la période : c'est la taille de position qui varie, pas le risque.
Synthèse renvoyée par run_sizing, stratégie de démonstration non validée
Trades2 714
Taux de gain17,2 %
Somme des rendements+429,25 %
Rendement cumulé+1 905,21 %
Rendement annualisé+189,52 %
Drawdown maximal−56,05 %
Sharpe2,772
Profit factor1,140
Calmar3,381
Gain moyen / perte moyenne+5,917 % / −1,042 %
Une perte moyenne de 1,04 % correspond au risque de 1 % plus les frais.
Tableau des trades après sizing : prix visé, prix exécuté et écart
Après sizing, chaque trade conserve son prix visé, son prix exécuté et l'écart entre les deux.

Le break-even est armé sur une bougie et devient actif à la suivante ; sa sortie porte le libellé BE. Un mode optionnel traite la cible comme un ordre limite, avec un état d'attente et des événements de non-exécution.

05Lire les trades

Retrouver pour chaque sortie sa raison, ses prix et ses coûts

metrics["trades_df"] rassemble pour chaque trade le sens, les bougies d'entrée et de sortie, les prix visés et exécutés, le rendement brut et net, les excursions, le setup, le profil et la phase. Les colonnes de coûts permettent de revenir sur chaque exécution.

metrics trades_df de l'exemple EMA : entry_time, exit_time, entry, exit, side, return, raw_return, commission_cost
Le journal de l'exemple EMA. Sur les premières lignes, le rendement brut d'environ −0,100 % correspond au SL de 0,1 % ; le rendement net ajoute la commission et le spread.
CodeSortieCodeSortie
1SL10Passage à plat forcé
2TP11Sortie forcée par la stratégie
3Break-even12Durée maximale
4 à 6TP sur EMA ou croisement13TP partiel
7Stop du trailing14Stop de groupe
8Signal de sortie15Invalidation
9Retournement16 et 17Action locale, appel de marge

Une sortie partielle forme une ligne distincte, avec le même trade_uid que le trade d'origine. Compter les lignes ne suffit donc pas à compter les positions.

06Mettre à l'épreuve

Vérifier que le signal apporte quelque chose, à gestion identique

Pour tester une idée de gestion, il suffit de conserver les entrées et de ne changer que le profil de sortie. Comparer les rendements en multiples du risque, le drawdown et le temps de récupération année par année permet ensuite de situer l'effet dans le temps.

Pour tester le signal lui-même, la référence aléatoire doit poser la même question. random_entry_keep_exit_logic relance le moteur avec des entrées tirées au hasard et la même logique de sortie. Une référence à durée de détention fixe, qui ignore stops et objectifs, ne répond pas à cette question.

engine.report_random_baseline(
    metrics=metrics,
    null_model="random_entry_keep_exit_logic",
    n_sim=20,
)
Vingt simulations suffisent pour une première lecture. Chaque simulation est un run complet ; ce nombre n'est pas un seuil statistique.

La validation se déclare dans le constructeur, avant les premiers essais : compteur d'essais, freeze, hors échantillon sur l'actif, puis cross-asset. DSR, Monte-Carlo, rétention hors échantillon, concentration de l'edge et dépendance aux valeurs extrêmes apportent des lectures complémentaires.

Voir un parcours de validation complet, publié sur le mode allocation →

07Le lien avec Research

Afficher le backtest dans le graphique du mode Research

Research · le signal

Calculer l'indicateur, mesurer ce qui suit ses signaux, comparer les versions.

Algo · la stratégie

Choisir les entrées, gérer les positions, appliquer les frais et le sizing.

En commun : le graphique, le serveur local, le manifeste de freeze et le registre des essais.

Research sert en amont à écrire et examiner l'indicateur. Algo ajoute les règles d'entrée, la gestion, l'exécution et le risque. Ces choix doivent être formalisés : partager la vue ne transforme pas automatiquement une étude en stratégie.

@engine.research_view(name="Showcase · EMA 50/100 · régime H1",
                      asset="XAUUSD", pip_size=0.01)
def plot_showcase(plot, c):
    price = plot.price()
    price.line(c.ema1, name="EMA 50", stroke=Stroke(color="#FFB74D", width=1.5))
    price.line(c.ema2, name="EMA 100", stroke=Stroke(color="#64B5F6", width=1.5))
    price.regime(c.regime, mode="box", bounds="segment_ohlc", styles=regime_styles)
    # bougies, entrées et sorties exécutées ajoutées automatiquement
La vue déclarée avant le run. Les colonnes EMA sont ajoutées pour l'affichage avec engine.bind_features, sans modifier les entrées du moteur.
Trades du backtest EMA 50/100 sur le graphique Research, zones de régime et sorties
Le résultat : chaque entrée (triangle) et chaque sortie (croix) correspond à un trade exécuté par le moteur. L'affichage ne compte aucun essai et ne touche pas au freeze.
08Compilation et limites

Savoir ce qui demande une recompilation, et ce qui reste à vérifier

Les profils et les règles sont transformés en matrices de paramètres : les modifier ne demande pas de recompiler un noyau déjà chargé. Modifier une fonction @njit du package invalide en revanche son cache.

CheminOrdre de grandeur de la première compilation
Sans système de sorties ni régimeNoyau spécialisé : environ 10 minutes.
Avec système de sorties ou régimeNoyau complet : 20 à 35 minutes.
Runs suivants dans la même sessionLe noyau compilé est réutilisé ; changer un paramètre reste instantané.

Ces durées sont mesurées sur l'environnement de développement et ne constituent pas un benchmark. C'est aussi pour éviter ce coût que le mode Research étudie un signal sans compilation.

Limites connues

Timing des règles de sortie

Les features et excursions de la bougie en cours peuvent être lues avant une sortie partielle valorisée à son ouverture. Le comportement du drapeau sl_tp_be_priority reste lui aussi à vérifier. Ces chemins demandent une validation dédiée.

Conditions et sens du trade

OnFeature compare une valeur brute sans tenir compte du sens long ou short : il faut donc déclarer les règles par côté. Un opérateur inconnu, par exemple eq, est actuellement converti sans avertissement en gt.

Backend et frais en mono-actif

Le backend Python des stratégies de sortie est accepté mais pas exécuté : les stratégies personnalisées passent par Numba. En mono-actif, ExecutionCostSpec(assets={...}) est ignoré ; les frais se déclarent à la racine.

Ordres en attente et code publié

Un chemin run_orders existe pour les ordres en attente ; il n'a pas encore été audité. Le dépôt public est un instantané du moteur Algo d'avril 2026 : toutes les fonctions décrites ici n'y figurent pas encore.

Arnaud Barbier · octobre 2026. Les captures servent à montrer le fonctionnement ; aucun résultat de performance n'est revendiqué.Dépôt public (instantané) →
Backtest engineAlgo mode · October 2026

Algo mode, from signal to managed trades, with costs and sizing

Algo mode turns a signal into a complete strategy. The user chooses the entries, describes how each position is managed and sets up costs and position size; the engine, compiled with Numba, runs the backtest and returns every trade with its target price, its executed price and the reason it closed.

Two strategies sharing the same signal can end up far apart through position management alone, and management is the part that gets tested least. Algo mode therefore describes it on four levels, from a simple TP and SL up to a compiled function, and tests it on its own: keep the entries and change only the exit, or draw the entries at random and keep the same exit logic. Sizing can be changed without rerunning the backtest. The signal itself can be studied first in Research mode.

4levels to describe position management
17exit codes, each recorded in the trade log
2,714trades resized from the tape, without rerunning the kernel
1%of equity risked per trade in the sizing example
Preview
Declaration of two exit profiles, trend_profile and fast_profile
Profiles. Two exit profiles, one to follow the trend, the other faster. They are compiled once, then assigned by setup or by regime. Click a screenshot to enlarge it.
EMA 50/100 backtest with an H1 regime shown in the Research chart
Trades. The backtest shown in the Research mode chart: H1 regimes as coloured zones, entries as triangles, exits as crosses.
metrics trades_df table: dates, entry and exit prices, gross and net return, commission
The log. metrics["trades_df"]: one row per exit, with entry and exit prices, gross and net return, commission and spread.
Equity curve in dollars and risk per trade held at 1%
Sizing. The same trade tape resized at 1% risk per trade: equity curve in dollars and the risk taken on each trade.
01From signal to trade

What the user describes and what the engine handles

Algo mode handles the whole backtest of a single-asset strategy. The user describes the logic: indicators, entry conditions and management rules. The engine applies the execution model, then returns returns, metrics and the trade log.

  1. 01Features

    Indicators and higher-timeframe data, aligned on the main series.

  2. 02Setups

    Long and short scores, choice of an entry and the regime.

  3. 03Execution

    Timing, exit profiles, management rules and costs.

  4. 04Result

    Trade log, tape for sizing, chart and validation tests.

The path of a single-strategy backtest. Allocation and investment modes are covered on their own pages.

Choosing between several setups

Each setup produces five columns: long and short scores, active states and an identifier. DecisionConfig keeps the best score and breaks ties with the chosen rule. The setup identifier then follows the trade and can select its exit profile.

Adapting to the market regime

A regime can block or weight an entry, then change its exit profile or strategy. It is supplied as raw identifiers: the engine applies availability and action delay itself, with no manual shift.

02Indicators and causality

Using H1 data only once its candle has closed

A feature is a function that reads the engine's columns, for example f.col("Close"), and returns one or several series. The computed columns then join the engine, and exit rules can use them by name.

10:00 → 11:00H1 candle forming

Its final value cannot feed M15 decisions taken before it closes.

From 11:00H1 value available

bar_close aligns the value at the right time; max_age discards a value that is too old.

feature.add(
    my_indicator, "h1_trend",
    on_ohlcv_matrix="algo_h1", matrix_asset="XAUUSD",
    align_to="main", availability="bar_close",
    source_tf="H1", max_age="2h",
)
Declaring an H1 feature on an M15 series. A plain forward-fill from 10:00 would hand over the final value an hour too early.

BacktestConfig then sets timing, sessions, candle filters, the maximum number of entries, the cooldown and default exits. With entry_delay=1, the entry happens at the next candle's open; executing on the signal candle would often be too optimistic.

03Four levels of management

Describing position management, from a simple setting to a compiled function

Two strategies sharing the same signal can end up far apart depending on how their positions are managed. The engine lets you describe that management on four levels, each more precise than the last:

LevelWhat you declareCost of a change
01 · ConfigurationTP, SL, break-even, trailing, maximum duration and flattening, in BacktestConfig.No recompilation
02 · ProfilesA named ExitProfileSpec, chosen by setup or regime. Fields left empty fall back to the configuration.No recompilation
03 · Phases and rulesPhaseSpec objects holding PositionRule entries: triggers, partial exits, adds, stop moves.No recompilation
04 · njit strategyA custom function through ExitStrategySpec, with local or global state.Recompilation when the code changes
Code: two ExitProfileSpec objects, trend_profile and fast_profile, and their mapping to an identifier
The two profiles of the EMA example: trend_profile (TP 1%, SL 0.1%) and fast_profile (TP 0.4%, SL 0.1%). Setup 0 uses trend_profile by default; the regime can override it.

With phases, a position starts in phase 0. A rule can trigger on an excursion, a multiple of risk, a bar count, a feature or a combination of conditions. It can then take partial profit, move the stop, change the target, add to the position or move to the next phase.

  1. 01Enter

    The setup picks the profile and the initial risk.

  2. 02Trigger

    A rule in the current phase watches its condition.

  3. 03Act

    Take a part, protect the stop or add.

  4. 04Change phase

    Apply the rules planned for the rest of the trade.

An example of declarative logic.

PartialConfig, PyramidConfig and AveragingConfig describe scaled exits, adds to winning positions and adds to losing positions. A global state can also cap simultaneous positions or impose a pause after several consecutive stops.

04Costs and sizing

From theoretical price to executed price, then to position size

Applying costs

ExecutionCostSpec describes the contract, lot size, spread, commissions and slippage. For a single asset, these settings are declared at the root of the spec.

Slippage is always adverse and depends on the previous candle's range, with a heavier penalty on stops. If the price opens beyond the stop, the exit fills at the open.

Sizing without rerunning the backtest

With return_tape=True, the run produces a trade tape. engine.run_sizing(...) recomputes sizes without rerunning the kernel, so several sizing rules can be compared in seconds.

Changing the entries or their management, on the other hand, needs a new backtest.

execution_costs = ExecutionCostSpec(
    enabled=True, product_type="cfd",
    contract_spec=ContractSpec(instrument_id="XAUUSD",
                               contract_size=100.0, quote_currency="USD"),
    spread_points=0.02,                      # price gap, not a %
    commission_per_lot_round_turn_usd=3.0,   # entry + exit
    slippage=SlippageSpec(enabled=True, adverse_only=True,
                          model="simple_range", simple_range_frac=0.0025,
                          stop_market_mult=1.25),
)

rets, metrics, tape = engine.run(..., execution_costs=execution_costs,
                                 return_tape=True)
sized = engine.run_sizing(tape=tape, sizing=sizing,
                          execution_costs=execution_costs,
                          strict_tape_context=True)
A cost declaration for gold, then sizing applied to the run's tape. The strict context checks that the tape matches the run.
Equity curve in dollars from July 2023 to 2026 and risk per trade held at 1%
The EMA example's tape resized at 1% risk per trade. Below, the risk taken stays constant over the whole period: position size varies, not risk.
Summary returned by run_sizing, unvalidated demonstration strategy
Trades2,714
Win rate17.2%
Sum of returns+429.25%
Cumulative return+1,905.21%
Annualised return+189.52%
Maximum drawdown−56.05%
Sharpe2.772
Profit factor1.140
Calmar3.381
Average win / average loss+5.917% / −1.042%
An average loss of 1.04% is the 1% risk plus costs.
Trade table after sizing: target price, executed price and the gap
After sizing, each trade keeps its target price, its executed price and the gap between them.

Break-even is armed on one candle and becomes active on the next; its exit is labelled BE. An optional mode treats the target as a limit order, with a pending state and non-fill events.

05Reading the trades

Finding, for each exit, its reason, its prices and its costs

metrics["trades_df"] gathers for each trade the direction, entry and exit candles, target and executed prices, gross and net return, excursions, setup, profile and phase. The cost columns let you go back over every fill.

metrics trades_df of the EMA example: entry_time, exit_time, entry, exit, side, return, raw_return, commission_cost
The EMA example's log. On the first rows, the gross return of about −0.100% is the 0.1% SL; the net return adds commission and spread.
CodeExitCodeExit
1SL10Forced flatten
2TP11Forced exit by the strategy
3Break-even12Maximum duration
4 to 6TP on EMA or crossover13Partial TP
7Trailing stop14Group stop
8Exit signal15Invalidation
9Reversal16 and 17Local action, margin call

A partial exit is a separate row, with the same trade_uid as the original trade. Counting rows is therefore not the same as counting positions.

06Stress-testing

Checking that the signal adds something, with identical management

To test a management idea, keep the entries and change only the exit profile. Comparing returns in multiples of risk, drawdown and recovery time year by year then places the effect in time.

To test the signal itself, the random reference has to ask the same question. random_entry_keep_exit_logic reruns the engine with randomly drawn entries and the same exit logic. A fixed-holding reference, which ignores stops and targets, does not answer that question.

engine.report_random_baseline(
    metrics=metrics,
    null_model="random_entry_keep_exit_logic",
    n_sim=20,
)
Twenty simulations are enough for a first look. Each simulation is a full run; this number is not a statistical threshold.

Validation is declared in the constructor, before the first trials: trial counter, freeze, out of sample on the asset, then cross-asset. DSR, Monte Carlo, out-of-sample retention, edge concentration and dependence on extreme values add further readings.

See a complete validation run, published on allocation mode →

07The link with Research

Showing the backtest in the Research mode chart

Research · the signal

Compute the indicator, measure what follows its signals, compare versions.

Algo · the strategy

Choose entries, manage positions, apply costs and sizing.

Shared: the chart, the local server, the freeze manifest and the trial registry.

Research is used upstream to write and examine the indicator. Algo adds entry rules, management, execution and risk. Those choices have to be made explicit: sharing the view does not turn a study into a strategy on its own.

@engine.research_view(name="Showcase · EMA 50/100 · H1 regime",
                      asset="XAUUSD", pip_size=0.01)
def plot_showcase(plot, c):
    price = plot.price()
    price.line(c.ema1, name="EMA 50", stroke=Stroke(color="#FFB74D", width=1.5))
    price.line(c.ema2, name="EMA 100", stroke=Stroke(color="#64B5F6", width=1.5))
    price.regime(c.regime, mode="box", bounds="segment_ohlc", styles=regime_styles)
    # candles, executed entries and exits are added automatically
The view declared before the run. The EMA columns are added for display with engine.bind_features, without changing the engine's inputs.
Trades of the EMA 50/100 backtest on the Research chart, regime zones and exits
The result: each entry (triangle) and each exit (cross) is a trade executed by the engine. Displaying it counts no trial and leaves the freeze untouched.
08Compilation and limits

Knowing what needs recompiling, and what still needs checking

Profiles and rules are turned into parameter matrices: changing them does not recompile a kernel that is already loaded. Changing an @njit function in the package, however, invalidates its cache.

PathOrder of magnitude of the first compilation
No exit system and no regimeSpecialised kernel: about 10 minutes.
With an exit system or regimeFull kernel: 20 to 35 minutes.
Later runs in the same sessionThe compiled kernel is reused; changing a parameter stays instant.

These durations come from the development environment and are not a benchmark. Avoiding this cost is also why Research mode studies a signal without compilation.

Known limits

Exit-rule timing

The current candle's features and excursions can be read before a partial exit valued at its open. The behaviour of the sl_tp_be_priority flag also remains to be checked. These paths need dedicated validation.

Conditions and trade direction

OnFeature compares a raw value without regard to long or short, so rules have to be declared per side. An unknown operator, for example eq, is currently converted to gt without warning.

Backend and single-asset costs

The Python backend for exit strategies is accepted but not executed: custom strategies go through Numba. For a single asset, ExecutionCostSpec(assets={...}) is ignored; costs are declared at the root.

Pending orders and published code

A run_orders path exists for pending orders; it has not been audited yet. The public repository is an April 2026 snapshot of the Algo engine: not every feature described here is in it yet.