Comment un bot Discord orchestre Claude Code via tmux pour publier automatiquement scanner, daily, weekly et rétrospective — l'humain supervise, il ne déclenche plus. Architecture, schedules, pipeline scanner, gestion d'erreurs et prompt engineering.
Chaque jour à 7h, un briefing marché est publié. À 23h, un scan algorithmique de 6 000+ tickers tourne. Le dimanche à 18h, une revue hebdomadaire sort. Aucun humain n'appuie sur un bouton.
Avant l'automatisation, la publication financière sur DailyTickers était un processus manuel, fragile et inconstant. Un opérateur humain devait lancer chaque pipeline, vérifier les données, formatter le HTML, ajouter les tags GTM, mettre à jour l'index, committer et pusher. Le résultat : certains jours pas de daily (oubli, fatigue, voyage), des tags GTM manquants sur 15% des pages (tracking perdu), des formats de dates incohérents (tantôt anglais, tantôt français), des mises à jour radar oubliées, et surtout — des données stale parce que personne n'avait relancé le MCP.
Le système actuel repose sur une chaîne simple mais robuste : un bot Discord
(Node.js) déclenche des sessions Claude Code via tmux,
selon un calendrier défini dans schedules.json. Claude Code exécute le
pipeline complet — de la requête MCP à la publication GitHub Pages — avec des règles
immuables qui empêchent toute dérive (pas de données inventées, pas de publication
incomplète, pas de skip d'étape).
Ce n'est pas un guide pour construire un trading bot qui passe des ordres. C'est un guide pour construire un système de publication autonome qui produit des analyses financières vérifiées, les formate en HTML, et les publie sur un site statique — le tout piloté par une IA contrainte par des règles strictes. L'exécution des trades est un sujet séparé, couvert dans le guide Automated Trading.
Source : Anthropic — Claude Code CLI usage
L'architecture est volontairement linéaire. Pas de microservices, pas de queue, pas de Kubernetes. Chaque publication suit le même chemin :
schedules.json.
Notifications en temps réel dans un channel dédié.
claude -p "prompt"add_card.js → git commit →
git push → déploiement auto.
| Composant | Technologie | Rôle |
|---|---|---|
| Discord Bot | Node.js, discord.js | Scheduler (cron-like via schedules.json), slash commands
pour déclenchements manuels, notifications de succès/échec,
threads de logs par run |
| Claude Code CLI | claude -p |
Cerveau du pipeline. Lit le contexte projet (CLAUDE.md, ~97KB de conventions), appelle le MCP, génère le HTML, gère le git. Chaque type d'article a son sub-CLAUDE.md (template complet) |
| MCP Gateway | FastAPI, 30+ outils | Source de vérité pour les données marché. QueryData (quotes, bars, fundamentals), RunScreener (DSL filters), GetMarketOverview, OptimizeSizing. Authentification par token |
| GitHub Pages | Static hosting | Déploiement automatique sur push main. Zero config, zero build,
CNAME vers articles.dailytickers.com.
Pre-commit hooks régénèrent search index, sitemap, feed |
| Pre-commit hooks | Shell + Node.js | À chaque commit : rebuild search_data.js (482 documents), sitemap.xml (486 URLs), feed.xml (50 items). Garantit la cohérence sans intervention |
Le choix de Discord comme orchestrateur peut surprendre. Pourquoi pas un simple cron ? Trois raisons :
1. Monitoring natif. Chaque run poste son résultat dans un channel dédié. Un succès montre le lien vers l'article publié. Un échec montre le message d'erreur et le contexte. Pas besoin d'un dashboard séparé — Discord est déjà ouvert sur le téléphone.
2. Commandes manuelles. Slash commands pour déclencher un run hors-schedule, relancer un pipeline échoué, ou forcer un refresh. Utile quand un événement marché inattendu justifie une publication immédiate.
3. Threads de logs. Chaque run crée un thread avec les logs de progression. On peut suivre en temps réel où en est le scanner dans ses 45 minutes de pipeline sans se connecter en SSH.
Source : discord.js — Documentation
Quatre tâches tournent sur des schedules fixes. Chacune est définie dans
schedules.json avec son heure, sa fréquence et son prompt.
| # | Tâche | Schedule | Durée | Détail |
|---|---|---|---|---|
| 1 | Scanner | Lun–Ven, 23h | ~45 min | Scan algorithmique de 6 000+ tickers. 10 étapes : regime check → screening → anti-dilution → enrichissement → risk gating → scoring → génération → status page → publication |
| 2 | Daily Briefing | Tous les jours, 7h | ~15 min | 17 sections obligatoires : indices, secteurs, macro, crypto, commodities, forex, earnings, géopolitique, formation, trade of the day. Samedi = récap + bilan semaine. Dimanche = crypto-only |
| 3 | Weekly Review | Dimanche, 18h | ~30 min | 18 sections : revue de la semaine, performance indices, rotations sectorielles, macro outlook, risques, opportunités, semaine à venir, allocations, watchlist |
| 4 | Rétrospective | Vendredi, 23h | ~20 min | Bilan des trades scanner de la semaine. Outcomes réels (MCP bars), win rate, profit factor, meilleures/pires positions, leçons. Tourne après le scanner du vendredi |
Daily du samedi. Les marchés US sont fermés, mais il y a du contenu : récapitulatif de la séance de vendredi (souvent la plus volatile de la semaine), bilan hebdomadaire condensé, et preview de ce qui attend lundi (earnings, données macro, events géopolitiques). Le MCP fournit les données du vendredi — pas besoin de données live.
Daily du dimanche. Marchés fermés + pas de données de la veille à récapituler. Le contenu se concentre sur deux axes : crypto (marché 24/7, il se passe toujours quelque chose le weekend) et géopolitique (les décisions politiques ne s'arrêtent pas le dimanche). Format plus court, ~12 sections au lieu de 17.
Convention de date du scanner. Le dossier du scanner porte la date
de la prochaine séance de trading, pas celle de l'exécution. Un scan
lancé le mardi 16 à 23h crée le dossier scanner/20260617/ (mercredi).
Un scan lancé le vendredi 19 à 23h crée scanner/20260622/ (lundi).
Cette convention évite toute ambiguïté sur la validité des setups.
Les heures ne sont pas arbitraires. Le scanner tourne à 23h parce que les marchés US ferment à 22h (heure de Paris) — il faut les données de clôture. Le daily sort à 7h pour être lisible avant l'ouverture européenne. La weekly à 18h le dimanche laisse le temps de la lire avant le lundi matin. Chaque schedule est calé sur le rythme des marchés, pas sur la convenance technique.
Chaque schedule envoie un prompt structuré à Claude Code. Le prompt n'est pas un bloc de texte libre — c'est une invocation de skill avec du contexte :
// Exemple : prompt du scanner (simplifié) const prompt = ` Nous sommes le ${dateStr} à ${timeStr} (heure de Paris). C'est un ${dayOfWeek}. Lance le pipeline /scanner pour la prochaine séance (${nextTradingDay}). Utilise EXCLUSIVEMENT le MCP DailyTickers pour les données. Ne jamais inventer de données financières. Publie via add_card.js. Commit et push si tout est ok. `; // Le skill /scanner contient le playbook complet : // 10 étapes, chacune avec ses règles, ses checks, // et ses conditions d'arrêt.
Le prompt est court parce que le gros du travail est dans le skill et le CLAUDE.md. Le prompt fournit le contexte temporel (date, jour, prochaine séance) et les contraintes critiques (MCP obligatoire, pas d'invention). Tout le reste — les 17 sections du daily, les critères du scanner, le template HTML — est chargé automatiquement par Claude Code depuis les fichiers du projet.
Le scanner est la tâche la plus complexe du système. En 45 minutes, il analyse 6 000+ tickers, en filtre la grande majorité, enrichit les survivants avec des données MCP, vérifie l'absence de pièges (dilution, événements proches), calcule des scores, optimise les tailles de position, et génère un article HTML complet avec des setup cards interactives.
| Étape | Durée | Outil | Action |
|---|---|---|---|
| 1. Regime Check | ~2 min | GetRegimeProbability | Détermine le budget de risque : Risk-On (VIX < 15), Neutral (15–20), Early Risk-Off (20–28), Risk-Off (> 28). Ajuste le nombre de positions et la taille max |
| 2. Screening | ~5 min | RunScreener (DSL) | Filtres techniques et fondamentaux : volume > 500K, momentum, structure EMA, secteur. 100+ candidats |
| 3. Candlestick Scan | ~3 min | candlestick-scanner.js | Détection de patterns (engulfing, hammer, doji) sur le fichier universe local. Jamais le MCP pour le listing des tickers |
| 4. Anti-Dilution | ~10 min | SEC EDGAR | Vérification S-3, ATM programs, warrants actifs, fonds toxiques, convertibles obligatoires. Élimination impitoyable |
| 5. MCP Enrichment | ~8 min | QueryData | Quote, bars_daily, fundamentals, technicals pour chaque candidat survivant. Données fraîches, vérifiées |
| 6. Risk Gating | ~3 min | Calcul local + MCP | Market cap tiers (< $2B reject, $2–10B ×0.5, $10–50B ×0.7), corrélation matrice, proximité earnings, concentration sectorielle |
| 7. Scoring | ~5 min | OptimizeSizing | Score composite (technique + fondamental + risque), taille de position optimisée par ATR et régime |
| 8. HTML Generation | ~5 min | Claude Code | Article complet avec setup cards, ECharts, ticker-headers, métriques, table de synthèse, disclaimer |
| 9. Status Page | ~2 min | gen-status-page.js | Mise à jour du dashboard scanner avec positions ouvertes, performance live, time machine, historique |
| 10. Publish | ~2 min | add_card.js + git | Indexation dans scanner.json, mise à jour radar.json, commit, push, déploiement GitHub Pages |
L'étape anti-dilution seule a empêché 3 recommandations catastrophiques. INDO avait un programme ATM actif qui aurait dilué les actionnaires de 30%. PANW et HPE avaient des shelf registrations actives. Sans ce check, ces tickers auraient été publiés avec un grade A — et les lecteurs auraient pu investir dans des pièges à dilution. Chaque étape existe pour une raison. On ne skippe jamais sans accord explicite de l'opérateur.
Depuis la war room du 19 juin 2026, le scanner n'applique plus un plancher market cap uniforme de $50B. À la place, un sizing tiered :
// Market cap tiering — war room 2026-06-19 if (mcap < 2e9) return 'REJECT'; // < $2B → rejet if (mcap < 10e9) return size * 0.5; // $2–10B → demi-position if (mcap < 50e9) return size * 0.7; // $10–50B → position réduite return size; // $50B+ → position pleine
Ce tiering sera reversé si les tickers sub-$20B montrent un stop moyen supérieur à -6% sur 3 rétrospectives consécutives.
Source : SEC EDGAR — Company filings search
Dans un système autonome, les erreurs ne sont pas des exceptions — elles sont la norme. Le MCP expire, GitHub rate-limit, le contexte sature, le disque se remplit. Ce qui distingue un système fiable d'un système fragile, ce n'est pas l'absence d'erreurs — c'est la visibilité des échecs et la sûreté des dégradations.
| Défaillance | Fréquence | Détection | Recovery |
|---|---|---|---|
| Token MCP expiré | ~1×/semaine | Erreur auth sur QueryData | MCP HARD STOP immédiat. Pipeline suspendu. Notification Discord avec lien de reconnexion. Reprise manuelle après re-auth |
| Push git échoué | ~1×/mois | Exit code non-zéro sur git push |
Retry avec git pull --rebase puis re-push.
Si conflit : notification, résolution manuelle |
| Contexte saturé | Rare (scanner long) | Claude Code conversation compaction | Auto-géré par Claude Code : les messages anciens sont résumés, le contexte essentiel est préservé. Transparent |
| Rate limiting API | ~2×/mois | HTTP 429 ou timeout | Backoff exponentiel intégré au CLI Claude Code. Retry automatique jusqu'à 5 tentatives |
| Espace disque | Rare | Check pré-génération | Alerte si < 1GB libre. Nettoyage des sessions tmux terminées et des logs anciens |
Cinq règles sont gravées dans le CLAUDE.md du projet. Elles ne peuvent pas être overridées par un prompt, un skill, ou un agent. Elles forment le filet de sécurité du système :
// Les 5 règles immuables du système 1. MCP HARD STOP Si le MCP bloque (auth, timeout, réseau) OU renvoie des données incohérentes (NaN, prix aberrants, stale > 48h) → STOP IMMÉDIAT. Jamais substituer. Jamais inventer. 2. NO HALLUCINATION Ne JAMAIS fabriquer de données financières. Pas de prix estimés, pas de market cap "de mémoire", pas de 52W range deviné. 3. SIZE CHECK Ne pas push un HTML < 10KB. Un article complet fait 30-100KB. En dessous = génération incomplète. 4. READ BEFORE WRITE Toujours lire le JSON cible (daily.json, scanner.json) AVANT d'appeler add_card.js. Prévient les doublons. 5. NO SKIP Jamais skipper une étape du pipeline sans accord explicite de l'opérateur. Chaque étape existe pour une raison.
Chaque run produit une notification dans le channel dédié :
// Succès ✅ Scanner 20260619 publié 8 setups (3 Dynamic, 3 TKL, 2 Orbit) Régime : Neutral (VIX 17.2) https://articles.dailytickers.com/scanner/20260622/ Durée : 43m12s // Échec ❌ Scanner 20260618 SUSPENDU Raison : MCP HARD STOP — token expiré Étape : 5/10 (MCP Enrichment) Action requise : re-auth MCP puis relancer
Le pire scénario n'est pas un échec visible. C'est un succès silencieux avec des données fausses. Le 21 juin 2026, un fork agent a halluciné 100% des données d'une rétrospective H1 — META affiché à +32% alors qu'il était à -15%, l'énergie à +36% alors qu'elle était à -1%. L'article avait l'air parfait. Les chiffres étaient faux. C'est ce type de défaillance qui a motivé la règle MCP HARD STOP.
Source : Anthropic — Claude Code overview
Chaque run Claude Code a besoin de son propre terminal. Le scanner de 23h et la rétrospective du vendredi peuvent se chevaucher. Deux sessions qui partagent un terminal finissent par corrompre leurs sorties respectives. tmux résout ce problème avec des sessions nommées et isolées.
// Le bot Discord lance chaque pipeline dans une session tmux dédiée const { execSync } = require('child_process'); function launchPipeline(type, dateStr, prompt) { const session = `${type}-${dateStr}`; // Vérifier qu'une session du même nom n'existe pas déjà try { execSync(`tmux has-session -t ${session} 2>/dev/null`); console.log(`Session ${session} déjà active, skip`); return false; } catch (e) { // Session n'existe pas — on peut la créer } // Créer la session et lancer Claude Code execSync(`tmux new-session -d -s ${session} \ 'cd ~/GolandProjects/articles && \ claude -p "${prompt}" 2>&1 | \ tee ~/logs/${session}.log; \ echo "EXIT_CODE=$?" >> ~/logs/${session}.log'`); // Activer la capture de logs execSync(`tmux pipe-pane -t ${session} \ 'cat >> ~/logs/${session}-pipe.log'`); return true; } // Exemples d'appels launchPipeline('scanner', '20260619', scannerPrompt); launchPipeline('daily', '20260619', dailyPrompt); launchPipeline('weekly', '20260621', weeklyPrompt);
| Session | Pattern | Exemple |
|---|---|---|
| Scanner | scanner-YYYYMMDD |
scanner-20260619 |
| Daily | daily-YYYYMMDD |
daily-20260619 |
| Weekly | weekly-YYYYMMDD |
weekly-20260621 |
| Rétrospective | retro-YYYYMMDD |
retro-20260619 |
// Vérifier les sessions actives function getActiveSessions() { try { const output = execSync( 'tmux list-sessions -F "#{session_name}|#{session_activity}"', { encoding: 'utf8' } ); return output.trim().split('\n').map(line => { const [name, activity] = line.split('|'); return { name, lastActivity: new Date(parseInt(activity) * 1000) }; }); } catch (e) { return []; } } // Nettoyer les sessions terminées (inactives > 2h) function cleanupSessions() { const sessions = getActiveSessions(); const cutoff = Date.now() - 2 * 3600000; sessions.forEach(s => { if (s.lastActivity.getTime() < cutoff) { execSync(`tmux kill-session -t ${s.name}`); console.log(`Cleaned up session: ${s.name}`); } }); }
Toujours inclure l'heure (HH:MM) dans les updates de progression. Quand un scanner prend 43 minutes, il est impossible de débugger un problème sans savoir à quelle heure chaque étape a commencé et fini. « Étape 4 terminée » ne dit rien. « [23:17] Étape 4 anti-dilution terminée — 3 tickers éliminés (INDO, PANW, HPE) » dit tout.
Le vendredi soir à 23h, deux tâches démarrent : le scanner (schedule #1) et, quand le scanner termine, la rétrospective (schedule #4). Grâce à tmux, les deux tournent dans des sessions séparées. La rétrospective attend que le scanner ait pushé ses résultats (elle a besoin des setups du jour pour les ajouter à son bilan). Le bot vérifie le commit du scanner avant de lancer la rétro :
// Attendre que le scanner ait pushé avant de lancer la rétro async function waitForScannerThenRetro(dateStr) { const maxWait = 60 * 60000; // 60 min max const start = Date.now(); while (Date.now() - start < maxWait) { const log = fs.readFileSync( `~/logs/scanner-${dateStr}.log`, 'utf8' ); if (log.includes('git push origin main') && log.includes('EXIT_CODE=0')) { launchPipeline('retro', dateStr, retroPrompt); return; } await sleep(30000); // Check toutes les 30s } notify('⚠️ Scanner timeout — rétrospective non lancée'); }
Source : tmux — terminal multiplexer manual
Le prompt envoyé par le bot Discord à Claude Code n'est que la pointe de l'iceberg. Le vrai travail de « prompt engineering » est dans les fichiers de contexte persistant que Claude Code charge automatiquement à chaque session.
Un bon prompt de publication contient exactement 4 éléments :
| Élément | Rôle | Exemple |
|---|---|---|
| Contexte temporel | Date, jour, heure — le modèle n'a pas de notion fiable du temps | Nous sommes le jeudi 18 juin 2026. Marchés ouverts. |
| Invocation de skill | Active le playbook complet de la tâche | Lance le pipeline /scanner pour la prochaine séance (vendredi 19 juin). |
| Contraintes critiques | Rappels des règles immuables dans le contexte du prompt | Utilise EXCLUSIVEMENT le MCP. Ne jamais inventer de données. |
| Output attendu | Ce qui doit se passer à la fin | Publie via add_card.js. Commit et push si tout est ok. |
Claude Code charge automatiquement une pyramide de fichiers de contexte, du plus général au plus spécifique :
scanner-pipeline, daily-weekly-analysis-workflows,
mcp-gateway-tools, etc.
Total : ~97KB (~24 000 tokens) de contexte persistant. C'est le « cerveau » du système. Chaque modification du CLAUDE.md change le comportement de tous les runs futurs — c'est pourquoi on le versionne dans git et on le traite comme du code, pas comme de la documentation.
Au lieu de tout mettre dans le prompt, on référence un skill — un playbook versionné qui contient le workflow complet :
// ❌ AVANT : prompt monolithique (500+ mots) const prompt = ` Nous sommes le 18 juin 2026. Lance le scanner. Étape 1 : appelle GetRegimeProbability... Étape 2 : appelle RunScreener avec les filtres... Étape 3 : pour chaque ticker, vérifie sur SEC EDGAR... Étape 4 : appelle QueryData types=quote,bars_daily... ... (200 lignes de plus) `; // ✅ APRÈS : invocation de skill (4 lignes) const prompt = ` Nous sommes le jeudi 18 juin 2026. Lance le pipeline /scanner pour la prochaine séance (vendredi 19 juin). MCP obligatoire. Commit et push si ok. `; // Le skill scanner-pipeline.md (25KB) est chargé // automatiquement quand Claude Code détecte le mot "scanner" // dans le prompt. Pas besoin de le répéter.
L'avantage est triple : le prompt est court (moins de tokens par run = moins cher), le playbook est versionné (modifiable sans toucher au bot), et les mises à jour s'appliquent à tous les runs futurs sans redéploiement.
| Dimension | Prompt brut | Skill-backed |
|---|---|---|
| Longueur prompt | ~500 mots | ~40 mots |
| Contexte total | ~500 mots (prompt seul) | ~97KB (CLAUDE.md + skills) |
| Maintenance | Modifier le bot + redéployer | Modifier le fichier skill + git push |
| Cohérence | Chaque prompt peut diverger | Un seul playbook pour tous les runs |
| Coût par run | ~500 tokens de prompt | ~50 tokens de prompt (le reste est cache) |
Le CLAUDE.md n'est pas un README. C'est du code qui s'exécute à chaque session. Une faute de frappe dans une règle CLAUDE.md peut provoquer un comportement incorrect sur tous les runs futurs. On le review comme du code, on le teste comme du code, et on le versionne comme du code. Quand on ajoute la règle « MCP HARD STOP », elle prend effet immédiatement sur le prochain run — pas besoin de redéployer quoi que ce soit.
Le système décrit dans cet article n'a pas été construit en un jour. Il a commencé par un seul schedule (le daily briefing), avec un prompt brut de 200 lignes et un cron job. Puis les erreurs ont forgé les règles. Le MCP HARD STOP est né d'un incident réel. L'anti-dilution check est né d'une recommandation catastrophique évitée de justesse. Le tiering market cap est né d'une war room. Chaque contrainte du système est une cicatrice — elle marque un problème qui s'est produit et qui ne se reproduira plus.
DailyTickers utilise Claude Code en abonnement (Max plan à $200/mois), pas l'API au token. Cela change fondamentalement l'économie : le coût est fixe, pas proportionnel au volume.
| Modèle de tarification | Coût mensuel | Adapté pour |
|---|---|---|
| Claude Code Max ($200/mois) | $200 fixe | Usage intensif (60+ runs/mois), modèle Opus, pas de surprise |
| API directe (Opus 4, $75/M output) | ~$500–600 | Intégration programmatique, facturation au token |
| API directe (Sonnet 4, $15/M output) | ~$100–150 | Volume élevé, tâches moins complexes |
À 60 runs/mois en Opus via l'API, le coût dépasserait $500. L'abonnement Max à $200 est 2–3× plus économique pour un usage quotidien intensif. Le compromis : on dépend des quotas de l'abonnement (qui peuvent varier) plutôt que d'un budget prévisible au token.
Avec l'abonnement, le coût marginal d'un article supplémentaire est effectivement $0 tant qu'on reste dans les quotas. C'est ce qui rend viable la publication de ~60 articles/mois avec données temps réel et mise en page professionnelle.
Ce guide fait partie d'une série technique sur l'infrastructure DailyTickers :