GUIDE TECHNIQUE — TRADING & INFRASTRUCTURE

Broker Simulator : Valider vos Trades en Miroir avant le Passage Live

Comment un système indépendant qui exécute les mêmes trades en parallèle permet de valider la fiabilité de votre tracking de positions — et de migrer la source de vérité sans risque, sans big-bang, et sans downtime.

Parallel-Run
20 min
Réconciliation
Cutover Automatique

Pourquoi Simuler avant de Faire Confiance

Vous avez construit un système de trading algorithmique. Chaque nuit, il scanne 6 000+ tickers, sélectionne les meilleurs setups, calcule les positions, applique les stops et les take-profits. Le pipeline génère un fichier pit-state.json qui représente l'état de chaque portefeuille à chaque instant. Mais comment savez-vous que ce fichier est correct ?

La réponse courte : vous ne le savez pas. Un bug de prix stale, un fill manqué, un arrondi de position, un stop qui se déclenche au mauvais moment — chacun de ces problèmes peut silencieusement corrompre votre état de portefeuille sans qu'aucun test unitaire ne le détecte. Parce que le problème n'est pas dans le code : il est dans l'interaction entre le code et les données réelles du marché.

La solution de DailyTickers : le Broker Simulator (simulator.dailytickers.com). Un service Go indépendant qui reçoit exactement les mêmes trades que le système principal, les exécute de façon autonome, puis compare ses résultats avec pit-state.json chaque nuit. Si les deux systèmes sont d'accord pendant 20 jours consécutifs, le simulateur devient la source de vérité. S'ils divergent : alerte immédiate, revert automatique, investigation.

0.0%
Divergence initiale lors du premier backfill du mode dynamic — parité parfaite dès le jour 1
20 jours
Seuil de convergence avant cutover automatique — confiance statistique, pas confiance aveugle
5 modes
Pilotés en parallèle : turbo, dynamic, balanced, bull, secured — chaque mode indépendamment validé
0 impact
Sur la production en cas de panne du simulateur — hard fallback sur pit-state.json systématique

Le principe du « miroir indépendant »

Le simulateur ne connaît rien des internals de DailyTickers. Il ne lit pas pit-state.json. Il ne connaît pas les scores, les régimes, ni les configurations des modes. Il reçoit des intents (« acheter 50 AAPL demain à l'ouverture ») et les exécute selon ses propres règles. C'est cette indépendance qui fait la valeur de la validation : si deux systèmes qui ne partagent aucun code arrivent au même résultat, la probabilité d'un bug commun est quasi nulle.

Ce guide technique détaille l'architecture complète du parallel-run : les 6 étapes du cycle autonome, le mécanisme de réconciliation, le cutover automatique avec auto-revert, et comment intégrer le simulateur dans votre propre pipeline de trading.

Source : Martin Fowler — Parallel Change (le pattern architectural derrière le parallel-run)

Architecture du Parallel-Run

Le parallel-run s'intègre dans le pipeline scanner nocturne sans le ralentir ni le bloquer. Chaque étape est non-bloquante : si le simulateur est indisponible, le scan continue normalement et un avertissement Discord est émis.

Vue d'ensemble

Scanner Pipeline Screening → Anti-dilution → Risk Gating → Scoring → Génération HTML
↓ intents (trades du jour)
publish-to-simulator.js Poste les intents d'entrée pour la prochaine séance au simulateur via REST API
↓ POST /api/accounts/{id}/mirror-order
Broker Simulator (Go) Service indépendant — exécute les ordres au next-open + VWAP gate, replay intraday pour SL/TP/horizon
↓ résultats indépendants
reconcile-simulator.js Compare sim vs pit-state.json : prix ±0.5%, NAV ±2%, positions identiques
cutover-decision.js 20 jours zéro-divergence → flip source-of-truth.json vers "sim"
↓ lecture conditionnelle
sim-source.js (read-switch) gen-api.js + gen-status-page.js lisent depuis le sim pour les modes cutover
Hard Fallback Toute erreur sim → retour automatique à pit-state.json. L'API publique ne casse JAMAIS.

Le simulateur : un service Go séparé

Le broker simulator est un service Go hébergé sur simulator.dailytickers.com, développé dans le repo dailystocks-platform. Il expose une API REST :

Endpoint Méthode Description
/api/accounts/{id}/mirror-order POST Soumettre un ordre miroir (achat/vente avec prix cible et stops)
/api/accounts/{id}/mirror-run POST Lancer le replay intraday (SL/TP/horizon/trailing exits)
/api/accounts/{id}/backfill POST Injecter l'historique gelé pour le bootstrap initial
/api/accounts GET Lister les comptes (auto-scoped par token de service)

Chaque mode de trading a un compte dédié dans le simulateur, nommé par convention mirror:<mode> (ex: mirror:dynamic, mirror:balanced). Le token de service scope automatiquement l'accès au workspace pilot — pas besoin de header X-Org-Id.

L'insight clé : l'indépendance

Le simulateur ne lit pas le code du scanner, ne connaît pas les scores, ne consulte pas les configurations de modes. Il reçoit des intents atomiques :

// Intent publié par publish-to-simulator.js
{
  "ticker": "AAPL",
  "side": "buy",
  "qty": 50,
  "entry": "next-open",
  "vwapGate": true,
  "stopLoss": 185.50,
  "takeProfit": 210.00,
  "horizonDays": 10,
  "trailingStop": { "activateR": 2.0, "lockPct": 0.5 }
}

Et les exécute selon ses propres règles de pricing et de timing. Si le résultat final (positions, P&L, NAV) correspond à celui de pit-state.json, c'est que les deux systèmes sont corrects — ou qu'ils ont exactement le même bug, ce qui est statistiquement improbable sur 20 jours.

Design non-bloquant

Si le token de service est absent (BROKERSIM_SERVICE_TOKEN non défini dans .env), le parallel-run est un no-op propre : le scan logge un WARNING et continue normalement. Le simulateur ne peut jamais bloquer la publication des résultats du scanner.

Source : GitHub — Deployment patterns (le parallel-run est une variante du canary deployment appliquée aux données)

Les 6 Étapes du Cycle Autonome

Chaque nuit, le pipeline scanner exécute les 6 étapes du parallel-run dans l'ordre. Chaque étape est idempotente (relançable sans effet de bord) et résiliente (une erreur à l'étape N n'empêche pas l'étape N+1).

1

Bootstrap-Once

Backfill de l'historique gelé pour les nouveaux modes. Exécuté une seule fois par mode, puis ignoré pour toujours.

export-to-simulator.js --sync
2

Mirror-Run

Exécution des intents d'entrée au next-open + VWAP gate. Replay intraday pour les exits (SL/TP/horizon/trailing).

run-mirror.js
3

Reconcile

Comparaison sim vs pit-state.json par mode. Prix ±0.5%, NAV ±2%. Append dans reconciliation-log.json.

reconcile-simulator.js
4

Cutover-Decision

Après 20 jours consécutifs sans divergence → flip source-of-truth.json vers "sim". Auto-revert sur toute divergence.

cutover-decision.js
5

Read-Switch

gen-api.js et gen-status-page.js lisent depuis le sim pour les modes cutover. Hard fallback sur pit-state.json si erreur.

sim-source.js
6

Publish

Après le sweep, poste les intents d'entrée du lendemain au simulateur pour préparer le prochain cycle.

publish-to-simulator.js

Étape 1 : Bootstrap-Once — Le point de départ

Quand un mode est ajouté au pilote (par exemple, le mode bull rejoint le parallel-run), le bootstrap injecte tout l'historique gelé de pit-state.json dans le compte mirror:bull du simulateur. C'est un backfill complet : toutes les positions ouvertes, tous les trades clôturés, l'equity courante.

// Commande
node tools/export-to-simulator.js --sync

// Ce qui se passe :
// 1. Lire les modes pilotes depuis simulator-config.json
// 2. Pour chaque mode sans fills dans mirror:<mode> → backfill
// 3. Pour chaque mode déjà backfillé → skip (idempotent)

// Résultat attendu :
✅ dynamic: already backfilled (skip)
✅ balanced: already backfilled (skip)
🔄 bull: backfilling 12 positions, 47 closed trades...
✅ bull: backfill complete — equity parity 0.0%

Le flag « déjà backfillé » est persisté côté simulateur. Même si vous relancez --sync 100 fois, le backfill ne se produit qu'une fois par mode.

Étape 2 : Mirror-Run — L'exécution indépendante

C'est le cœur du parallel-run. Le mirror-run prend les intents publiés la veille et les exécute selon les règles du simulateur :

Le simulateur utilise les données Yahoo Finance 1-minute pour le replay intraday, ce qui lui permet de détecter les touches de SL/TP avec une précision de 1 minute.

Étape 3 : Reconcile — La comparaison qui compte

La réconciliation compare, mode par mode, l'état du simulateur avec pit-state.json. Trois métriques sont vérifiées :

Métrique Tolérance Pourquoi cette marge
Prix moyen d'entrée ±0.5% Différences de timing d'exécution (ouverture vs VWAP)
NAV (Net Asset Value) ±2% MtM intraday, dividendes, corporate actions
Positions Exacte Même ticker, même quantité, même direction

Le résultat est appendé dans data/reconciliation-log.json, un fichier append-only qui constitue l'audit trail complet de chaque comparaison.

Étapes 4-6 : Cutover, Read-Switch, Publish

L'étape 4 (cutover-decision) est l'algorithme de promotion : si le mode a 20 entrées consécutives dans le log de réconciliation avec divergence = 0, le flag dans source-of-truth.json passe de "articles" à "sim". Le revert est automatique : dès qu'une divergence apparaît, le flag revient à "articles".

L'étape 5 (read-switch) fait que gen-api.js et gen-status-page.js lisent les positions depuis le simulateur pour les modes qui ont été promus. Le hard fallback est intégré : si l'appel au sim échoue (réseau, token expiré, service down), le système revient silencieusement à pit-state.json.

L'étape 6 (publish) est la boucle : après que le sweep a déterminé les trades du lendemain, les intents sont postés au simulateur pour que le prochain mirror-run puisse les exécuter.

Source : Martin Fowler — Canary Release

La Réconciliation : Comparer deux Vérités

La réconciliation est le moment de vérité du parallel-run. Chaque nuit, après le mirror-run, le script reconcile-simulator.js compare position par position, mode par mode, les résultats du simulateur avec ceux de pit-state.json.

Qu'est-ce qui est comparé

Pour chaque mode pilote (turbo, dynamic, balanced, bull, secured), la réconciliation vérifie :

Le format du log

// Entrée dans reconciliation-log.json
{
  "date": "2026-06-19",
  "mode": "dynamic",
  "status": "MATCH",
  "simEquity": 127450.32,
  "pitEquity": 127389.18,
  "equityDiffPct": 0.048,
  "positionsMatch": true,
  "openPositions": 8,
  "divergences": [],
  "consecutiveMatches": 14
}

// Entrée avec divergence
{
  "date": "2026-06-20",
  "mode": "balanced",
  "status": "DIVERGENCE",
  "simEquity": 98200.44,
  "pitEquity": 95100.77,
  "equityDiffPct": 3.26,
  "positionsMatch": false,
  "divergences": [
    { "type": "missing_position", "ticker": "CRDO", "inSim": true, "inPit": false }
  ],
  "consecutiveMatches": 0
}

Types de divergences

Type Signification Cause probable Action
missing_position Position dans un système mais pas l'autre Fill manqué, intent non publié, ou bug de clôture Investiguer le log d'intents, vérifier le mirror-order
price_drift Prix moyen diverge de >0.5% Timing d'exécution différent, données Yahoo stale Vérifier les fills bruts des deux côtés
nav_mismatch Equity du mode diverge de >2% Split/dividende non appliqué, cash accounting Recalculer le MtM manuellement, vérifier corporate actions
qty_mismatch Même ticker mais quantités différentes Partial fill, sizing différent, arrondi Comparer les ordres originaux et les fills
staleness_artifact Grande divergence NAV sur positions backfillées Yahoo 1m limité à ~30 jours — pas de replay précis Attendu au début, se résout naturellement (forward-only)

L'artefact de staleness

Lors du premier backfill, les positions anciennes (ouvertes depuis plus de 30 jours) ne peuvent pas être rejouées en intraday car Yahoo Finance ne fournit les données 1-minute que sur les ~30 derniers jours. Cela crée une divergence NAV initiale qui est un artefact, pas un bug. Les trades forward (à partir du moment où le parallel-run est actif) ne sont pas affectés. La divergence se résorbe naturellement à mesure que les vieilles positions se ferment.

Alertes Discord

Chaque divergence déclenche une alerte Discord dans le canal de monitoring, avec :

Les matchs ne déclenchent pas d'alerte (bruit inutile) — seul le franchissement du seuil de 20 jours génère une notification positive (AUTO-CUTOVER).

Source : Wikipedia — Reconciliation (accounting)

Le Cutover Automatique : Zéro Humain dans la Boucle

Le cutover est le moment où le simulateur passe de « miroir de validation » à « source de vérité ». C'est la transition la plus critique du système, et elle est entièrement automatique — par design.

L'algorithme de cutover

// Pseudo-code de cutover-decision.js
for (const mode of pilotModes) {
  const log = readReconciliationLog(mode);
  const recent = log.slice(-20);  // 20 dernières entrées

  const currentSource = readSourceOfTruth(mode);
  const allMatch = recent.length === 20
    && recent.every(e => e.status === 'MATCH');
  const latestDiverged = log.at(-1)?.status === 'DIVERGENCE';

  if (allMatch && currentSource === 'articles') {
    // PROMOTION : 20 jours consécutifs de convergence
    flipSourceOfTruth(mode, 'sim');
    discordAlert(`AUTO-CUTOVER: ${mode} → sim`);
  }

  if (latestDiverged && currentSource === 'sim') {
    // REVERT : divergence détectée sur un mode promu
    flipSourceOfTruth(mode, 'articles');
    discordAlert(`AUTO-REVERTED: ${mode} → articles`);
  }
}

Pourquoi 20 jours ?

Le seuil de 20 jours de trading n'est pas arbitraire :

source-of-truth.json

// data/source-of-truth.json
{
  "dynamic": "sim",       // ← cutover effectué après 20j
  "balanced": "articles",  // ← encore en validation (jour 14)
  "turbo": "articles",     // ← en validation (jour 7)
  "bull": "articles",      // ← récemment onboardé (jour 3)
  "secured": "articles"    // ← en validation (jour 11)
}

Ce fichier est la seule source de décision pour le read-switch. Un simple JSON.parse suffit pour savoir d'où lire les données de chaque mode.

Le Hard Fallback : la production ne casse jamais

C'est la règle la plus importante du système : l'API publique ne peut jamais être vide, en erreur, ou stale à cause du simulateur. Le fallback est implémenté dans sim-source.js :

async function getPositions(mode) {
  const source = readSourceOfTruth(mode);

  if (source === 'sim') {
    try {
      const simData = await fetchFromSimulator(mode);
      if (isStale(simData)) throw new Error('stale sim data');
      return simData;
    } catch (err) {
      // HARD FALLBACK — toujours pit-state.json
      console.warn(`Sim error for ${mode}: ${err.message} — falling back`);
      return readPitState(mode);  // JAMAIS null, JAMAIS vide
    }
  }

  return readPitState(mode);  // source = 'articles'
}

Ce design garantit trois propriétés :

Propriété Garantie Comment
Disponibilité L'API publique retourne toujours des données Le fallback lit un fichier local (pas de réseau)
Fraîcheur Les données ne sont jamais plus stale que pit-state.json Le check isStale() rejette les données sim trop vieilles
Réversibilité Un cutover peut être annulé en <1 seconde Un seul champ dans un fichier JSON

Anti-pattern : le big-bang cutover

Ne jamais basculer tous les modes en même temps. Le cutover par mode permet de limiter le blast radius : si le mode dynamic a un problème après cutover, les 4 autres modes ne sont pas affectés. C'est le même principe que le canary deployment en infrastructure.

Source : Google SRE Book — Reliable Product Launches at Scale

Intégrer le Simulator dans votre Propre Pipeline

Le simulateur est conçu pour être intégré dans n'importe quel pipeline de trading qui produit des intents structurés. Voici comment connecter votre système.

1. Configuration du token

# Dans articles/.env (gitignored)
# IMPORTANT : valeur NON-QUOTÉE (loadEnv ne strip pas les quotes)
BROKERSIM_SERVICE_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# Alternative : export dans ~/.profile
export BROKERSIM_SERVICE_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Sécurité du token

Le token scope automatiquement l'accès au workspace pilot. Il ne permet pas d'accéder aux données d'autres workspaces. Ne le commitez jamais dans le repo — .env est dans .gitignore.

2. simulator-client.js : la bibliothèque client

Le fichier tools/lib/simulator-client.js encapsule toute la communication avec le simulateur :

const { SimulatorClient } = require('./lib/simulator-client');

// Initialisation (charge le token depuis .env automatiquement)
const sim = new SimulatorClient();

// Vérifier la connexion
const accounts = await sim.listAccounts();
console.log(accounts);
// → [{ id: 'abc123', label: 'mirror:dynamic' }, ...]

// Publier un intent
await sim.mirrorOrder('abc123', {
  ticker: 'AAPL',
  side: 'buy',
  qty: 50,
  entry: 'next-open',
  vwapGate: true,
  stopLoss: 185.50,
  takeProfit: 210.00,
  horizonDays: 10
});

// Lancer le mirror-run
await sim.mirrorRun('abc123');

// Lire l'état pour la réconciliation
const simState = await sim.getAccountState('abc123');
console.log(simState.equity, simState.positions);

3. Ajouter un nouveau mode

Pour ajouter un mode au parallel-run, deux étapes suffisent :

  1. Ajouter le mode dans simulator-config.json (clé pilotModes)
  2. S'assurer que pit-state.json contient les données du mode

Au prochain run de export-to-simulator.js --sync, le mode sera automatiquement backfillé et intégré au cycle de réconciliation.

4. simulator-config.json

{
  "baseUrl": "https://simulator.dailytickers.com",
  "pilotModes": ["turbo", "dynamic", "balanced", "bull", "secured"],
  "tolerances": {
    "pricePct": 0.5,   // ±0.5% sur les prix moyens
    "navPct": 2.0,     // ±2% sur l'equity du mode
    "qtyExact": true   // quantités doivent matcher exactement
  },
  "cutoverDays": 20,    // jours consécutifs de convergence
  "workspace": "pilot"  // workspace du simulateur
}

5. Extension à de nouvelles classes d'actifs

Classe d'actif Statut Limitation
Equities US ✅ Actif Aucune — couverture complète via Yahoo 1m
Crypto 🔜 Planifié Binance fournit les données 1m, intégration en cours
Forex ❌ Exclu Pas de données intraday FX fiables et gratuites
Metals / Futures ❌ Exclu Pas de pricing intraday sur les futures metals

Source : The Twelve-Factor App — Config

Les Gains Concrets du Parallel-Run

Le parallel-run n'est pas un exercice académique. Il apporte des bénéfices concrets et mesurables à chaque étape de la chaîne de valeur.

1. Confiance vérifiable

Sans le simulateur, la seule façon de savoir si pit-state.json est correct est de le vérifier manuellement — un exercice fastidieux, sujet aux erreurs, et qui ne scale pas avec 5 modes et des dizaines de positions. Avec le parallel-run, la confiance est quantifiée : le compteur de jours consécutifs est une métrique objective de fiabilité.

2. Détection de bugs en 24h

Tout bug de tracking dans pit-state.json — un fill manqué, un stop déclenché au mauvais prix, un arrondi de quantité — apparaît comme une divergence dans la réconciliation du lendemain. Pas besoin d'attendre un audit mensuel ou une anomalie visible dans les résultats.

3. Migration sans risque

Le cutover progressif par mode transforme une migration terrifiante (« on bascule tout d'un coup ») en une série de micro-migrations réversibles. Chaque mode est promu individuellement, et un revert prend moins d'une seconde.

Comparaison : avant vs après

Dimension Sans Simulator Avec Simulator
Confiance dans les données Subjective — « ça a l'air correct » Quantifiée — 20 jours de convergence indépendante
Détection de bugs Semaines/mois — découvert à la prochaine rétrospective 24 heures — divergence détectée au prochain reconcile
Risque de migration Big-bang — tout ou rien, revert complexe Progressif — par mode, réversible en <1 seconde
Audit trail Inexistant — git blame sur pit-state.json Complet — reconciliation-log.json append-only
Impact d'une panne N/A (pas de second système) Zéro — hard fallback transparent
Coût opérationnel Vérifications manuelles chronophages Entièrement automatique, <2 min par nuit

Bugs que la réconciliation attraperait

Exemples concrets de problèmes que le parallel-run détecterait en 24h :

Source : Increment — Testing in Production

Construire la Confiance par la Vérification Indépendante

Le parallel-run du broker simulator incarne un principe fondamental en ingénierie des systèmes critiques : ne faites pas confiance — vérifiez. Mais vérifiez de façon structurée, automatique, et réversible.

Le pattern est transposable à tout système qui produit des données critiques : systèmes de facturation, pipelines de données financières, moteurs de recommandation, systèmes de scoring. À chaque fois que la question « est-ce que ces données sont correctes ? » est importante, un miroir indépendant est la réponse la plus robuste.

Les 6 principes du parallel-run

  • Indépendance totale — le miroir ne partage aucun code avec le système principal. C'est cette indépendance qui fait la valeur de la validation.
  • 20 jours de convergence — pas de cutover sur un coup de chance. La confiance se construit statistiquement, sur des données de marché réelles.
  • Auto-revert sur divergence — le filet de sécurité est permanent. Un bug post-cutover ne corrompt pas les données plus de 24h.
  • Hard fallback — la production ne casse jamais. Le simulateur est un bonus, pas une dépendance. pit-state.json est toujours là.
  • Non-blocking — le pipeline ne dépend pas du simulateur. Token absent = no-op propre + warning. Pas d'interruption de service.
  • Append-only audit trail — chaque réconciliation est persistée. L'historique complet est disponible pour l'analyse et le debugging.

Le résultat validé sur le mode dynamic — 0.0% de divergence dès le premier backfill le 18 juin 2026 — est un point de données encourageant. Mais c'est le processus qui compte, pas le résultat ponctuel : la réconciliation quotidienne, l'auto-revert, le cutover progressif. Ce sont ces mécanismes qui transforment un « ça marche aujourd'hui » en « ça marche, et on le sait ».

Articles liés

Multi-Broker & Ledger Interne

L'architecture multi-broker et le ledger append-only qui alimente le simulateur

Harnesses IA en Finance

Contraindre les modèles à produire des résultats fiables — le même esprit de validation appliqué à l'IA

Claude Code Trading Bot

Le bot Discord qui orchestre le pipeline scanner — dont le parallel-run fait partie

Source : Google SRE Book — Testing for Reliability

Pourquoi Simuler Architecture 6 Étapes Réconciliation Cutover Intégration Gains Concrets Conclusion