Arrête de me dire quand acheter.
C’est toute la demande. Ne dessine pas de flèches sur mon graphique. N’envoie pas de webhooks “SIGNAL D’ACHAT CONFIRMÉ”. Ne me fabrique pas un winrate bidon sorti d’un backtest. Ne me vends pas un Discord à 97 dollars par mois. Dis-moi juste où est le RSI en ce moment, où se trouve l’order block récent, où se situe le prix par rapport au VWAP de la session. La couche d’interprétation est la mienne. C’est la partie que je ne sous-traite pas.
Tous les SaaS d’analyse technique du web vendent l’inverse. Ils emballent quinze indicateurs dans une boîte noire, collent “AI-powered” sur la page marketing et facturent un abonnement mensuel pour un “signal” opaque qui tombe juste 51% du temps une bonne semaine. Le pitch, c’est “on a fait le plus dur à ta place”. Le plus dur, c’est précisément ce que tu ne dois jamais laisser faire par quelqu’un d’autre.
wickworks, c’est à quoi ressemble l’inverse de ça.
Des bougies entrent, des primitives sortent
docker run --rm -p 8000:8000 psyb0t/wickworks:latestDeux endpoints. GET /health renvoie { "ok": true, "version": "..." }. POST / prend tes bougies OHLC et une map des indicateurs que tu veux, et renvoie ces indicateurs. Rien d’autre. Pas d’état, pas de base de données, pas de queue, pas de mur d’authentification, pas de limitation de débit, pas de clé d’API. Le container est sans état et idempotent, mêmes bougies en entrée, mêmes octets en sortie, à chaque fois. Lance dix replicas derrière un load balancer et elles tombent toutes d’accord sur les maths.
curl -s -X POST https://ciprian.51k.eu00/
-H 'Content-Type: application/json'
-d '{
"bars": [ {"time":1700000000,"open":1.0832,"high":1.0851,"low":1.0828,"close":1.0844,"volume":1247}, ... ],
"indicators": {
"rsi": true,
"rsi21": { "type": "rsi", "length": 21 },
"macd": true,
"orderBlocks": true,
"fvg": true
}
}'Un seul nom de champ, volontairement: le volume de la bougie s’appelle volume. Depuis la v0.7.0, les orthographes façon MetaTrader ont disparu et sont activement rejetées, pas silencieusement acceptées et remappées: envoie tickVolume, realVolume, tick_volume ou real_volume et la requête échoue. Deux noms pour un seul nombre, c’est comme ça qu’on finit avec deux réponses différentes, donc il y en a un. Les bougies sont validées avant le moindre calcul, aussi: OHLC fini et positif, géométrie de chandelier qui tient debout, volume non négatif, et horodatages UTC uniques et strictement croissants. Demande un indicateur qui n’existe pas et tu récupères un 400 avec unknown_indicator au lieu d’un résultat silencieusement vide.
Les clés de ta map indicators sont les clés que tu récupères. Demande rsi, tu obtiens rsi. Demande rsi21 avec "type": "rsi" et une longueur, tu obtiens un second RSI sous ce nom-là. Empile quatre stochs avec des paramètres différents dans un seul appel, quatre clés, quatre objets, quatre sorties distinctes. L’API ne peut pas renvoyer des données que tu n’as pas demandées, et elle ne peut pas renvoyer de clés en double parce qu’un objet JSON n’en a pas. Tu ne peux pas t’en servir de travers.
Ce qu’il y a dans la boîte
Le catalogue couvre tout l’univers standard de l’analyse technique et un peu plus. Chaque sortie est en camelCase, sûre côté NaN (les positions de chauffe sont null, jamais un NaN littéral qui casse les parseurs en aval) et sérialisée à travers une couche de nettoyage, donc aucun numpy.float64(...) ne passe la porte.
- 18 moyennes mobiles: SMA, EMA, HMA, WMA, DEMA, TEMA, T3, KAMA, ALMA, linreg, JMA, ZLMA, RMA, FWMA, SWMA, sinwma, TRIMA, VWMA. Plus le VWAP ancré à la session, avec reset quotidien, hebdomadaire ou mensuel, et un paramètre
sessionOffsetpour que les sessions NY (-5h) ou EET (-2h) s’ancrent là où il faut. - Oscillateurs de momentum: RSI, MFI, Williams %R, CCI, ROC, MOM, Ultimate Oscillator, Stochastic, StochRSI, MACD, TSI, TRIX, Ehlers Fisher Transform.
- Force et direction de tendance: ADX avec +DI/-DI, Aroon, Vortex.
- Volatilité et bandes: ATR, NATR, Bollinger Bands, Keltner Channels, Donchian Channels, TTM Squeeze (avec des drapeaux d’état explicites
on/off/nopar bougie, la bougie de relâchement du squeeze est un événement discret que tu peux détecter, pas une supposition). - Signaux de tendance suiveurs: Supertrend, Parabolic SAR, Chandelier Exit, nuage Ichimoku. Choisis-en un, ce sont des variations sur la même idée avec des compromis lag/whipsaw différents.
- Volume et flux monétaire: OBV, A/D, CMF, A/D Oscillator, Klinger.
- Smart Money Concepts: Order Blocks, Fair Value Gaps, cassures de structure BOS/CHoCH, niveaux de swing, niveaux de S/R maison, zones de liquidité, retracements, sessions, plus hauts et plus bas de la période précédente.
Le schéma complet, avec chaque paramètre et chaque forme de retour, vit dans schema.json, du JSON Schema Draft 2020-12, bon pour générer automatiquement des clients typés dans le langage que tu veux.
SMC fait correctement
Smart Money Concepts, c’est moitié analyse de price action réellement utile, moitié contenu d’influenceur YouTube. Wickworks fait la moitié utile et saute le dogme.
Les Order Blocks livrent leurs signaux de fraîcheur au lieu d’être filtrés. Avant ça marchait dans l’autre sens: wickworks jetait les zones mitigées côté serveur et ne te rendait que les vivantes. C’était le mauvais choix, “mitigé” n’est pas une seule chose, et ce n’est pas au serveur de choisir ton critère. Donc depuis la v0.5.x, chaque order block revient avec trois signaux de fraîcheur indépendants et c’est toi qui décides ce qui compte comme consommé:
mitigated_wick: la mèche d’une bougie ultérieure a traversé la zone. Large, c’est le défaut de la bibliothèque et c’est ce qu’utilisait l’ancien filtre serveur.mitigated_close: le corps d’une bougie ultérieure a vraiment cassé au-delà. Plus strict, le critère “ça a l’air mitigé à l’œil”.touch_count: combien d’événements de contact distincts la zone a connus.
Les deux drapeaux de la bibliothèque ne se déclenchent que sur des cassures invalidantes, le prix a dépassé la zone dans le mauvais sens. touch_count se déclenche sur n’importe quelle intersection d’intervalle, donc il capte “le prix est venu voir ce niveau” sans exiger une cassure complète. Une zone touchée quatre fois et jamais cassée, ce n’est pas du tout la même chose qu’une zone défoncée une fois, et maintenant tu peux les distinguer au moment de tracer.
Le coût, c’est un appel smc.ob() de plus par analyse pour dériver les drapeaux du critère de clôture, et le plafond d’OB est passé de 20 à 40 pour compenser l’absence de filtrage. Les FVG restent plafonnés à 15. Tout est trié par ordre croissant de distance au prix actuel, les zones qui comptent encore, classées par la vitesse à laquelle tu les toucherais.
BOS et CHoCH sont des faits structurels, pas des signaux. Un Break of Structure (le prix sort le dernier swing dans le sens de la tendance), c’est le graphique qui dit “la tendance a continué”. Un Change of Character (le prix casse contre la structure de la tendance précédente pour la première fois), c’est le graphique qui dit “la tendance vient de se casser pour la première fois”. Wickworks émet l’événement avec son niveau et sa direction. Il n’émet pas “la tendance s’est retournée, achète maintenant”. Ça, c’est ta lecture de l’événement. Le graphique pose les faits, toi tu poses l’interprétation.
L’algorithme de niveaux S/R est maison et mérite d’être décrit. Tu prends les pivots de swing d’un détecteur de swing sur 7 bougies. Tu ne gardes que les pivots que le prix a testés ≥2 fois (un “test” = un plus haut ou un plus bas à moins de ½·ATR du niveau, avec des bougies entre les contacts). Tu imposes un espacement de ≥3·ATR entre les niveaux gardés, pour ne pas te retrouver avec trois niveaux presque identiques empilés et comptés comme trois. Tu renvoies jusqu’à trois niveaux les plus proches au-dessus du prix actuel (résistance) et trois les plus proches en dessous (support). C’est ce que la plupart des traders veulent dire quand ils tracent des lignes de S/R à la main, de vrais niveaux testés avec une séparation qui a du sens, et presque aucun indicateur automatique ne fait ça comme ça. La plupart renvoient la liste brute des swings et appellent ça du support.
Pas de signaux. Jamais.
C’est la position de la v0.3.0 et elle est porteuse pour tout le projet:
- Pas de détection de divergences.
- Pas d’événements de croisement MACD.
- Pas d’étiquettes “golden cross” ou “death cross”.
- Pas d’étiquettes d’achat ou de vente.
- Pas de drapeaux “ceci est un signal frais” sur les sorties d’événements.
Chaque valeur de la réponse est l’une de trois choses: une série d’indicateur brute, un fait structurel (un order block s’est formé sur cette bougie; le prix a clôturé au-delà de ce niveau de swing), ou un résumé numérique précuit là-dessus (dernière clôture, dernier RSI, position actuelle par rapport aux MM clés). Jamais un jugement. Tu veux des divergences, construis-les. Tu veux des événements de croisement, construis-les. Ils sont triviaux à calculer par-dessus les primitives que renvoie wickworks, ils vivent dans ton code, et tu peux itérer dessus sans redéployer un service Python ni payer qui que ce soit.
Cette position est toute la raison d’être de ce projet. Tout le reste, la taille du catalogue, la forme de la réponse, la suite de tests, la licence, en découle.
“Donne-moi juste l’état actuel”
Il y a un chemin rapide pour les clients qui ne veulent pas des Series complètes, juste l’instantané de la dernière bougie. Six sorties sans paramètres partagent une seule passe d’analyse, demande les six et elles coûtent le prix d’une:
"indicators": {
"price": true,
"levels": true,
"momentum": true,
"volume": true,
"position": true,
"slope": true
}price: dernière clôture.levels: EMA21, SMA50/100/200, ATR, VWAP, Donchian haut/bas/milieu.momentum: RSI, MFI, ligne et signal MACD plus l’histogramme, ADX, K/D stochastique.volume: ratio volume actuel sur volume récent, OBV, et un booléenisSpikequi vaut true quand le volume dépasse 2× la moyenne récente.position: pour chacune de EMA21/SMA50/100/200/VWAP:"above"ou"below". Carte de biais.slope: les mêmes clés,"up"ou"down"sur les 10 dernières bougies. Combine-la avecpositionet tu as le régime du graphique en deux objets courts.
Les preuves
Le modèle de confiance d’un service d’analyse technique, c’est “est-ce que les nombres correspondent à ce à quoi ils doivent correspondre”. Wickworks livre 370 tests en trois catégories:
- Diffs de maths en forme close. Pour chaque indicateur standard (RSI, MACD, ATR, Bollinger, Stochastic, Aroon, CCI, Williams %R, ROC, MOM, OBV, Donchian, VWMA, EMA, SMA), la suite de tests réimplémente la formule à zéro en numpy et pandas nus et compare la valeur de la dernière bougie à la sortie de wickworks sur de vrais ticks EURUSD H1. Tolérance
rtol=1e-5. Si pandas_ta dérive ou si notre câblage pourrit, le diff hurle avant même que l’image Docker se construise. - Parité smc_fast. La couche SMC livre un portage accéléré par numba de
smartmoneyconceptspour le chemin chaud. Huit tests prouvent une sortie identique octet pour octet à celle de la bibliothèque amont, sur les mêmes entrées. Le chemin rapide ne peut jamais produire silencieusement des nombres différents. - Contrat du pipeline. Déterminisme (mêmes bougies → mêmes octets de réponse). Stabilité à l’ajout (les indicateurs causaux ne réécrivent pas leur propre historique quand de nouvelles bougies arrivent). Comptage des null dans la zone de chauffe. Isolation des indicateurs (deux demandés ensemble donnent les mêmes nombres que deux demandés séparément). Les chemins d’erreur HTTP. Le contrat du champ volume.
Et le jeu de paquets est verrouillé derrière le mécanisme exclude-newer d’uv: toute version de dépendance publiée après une date fixe est refusée à la résolution du lock. La date est avancée automatiquement par les cibles Make qui touchent aux paquets, donc si tu ne lances pas make pkg-add ou make pkg-update, la date ne bouge pas, et une release malveillante fraîchement publiée, encore dans sa fenêtre de détection, ne peut pas se glisser dans un rafraîchissement passif du lock. Ennuyeux, paranoïaque, correct.
Il te dit quand tu as merdé
Demande un sma avec length: 200 alors que tu n’as envoyé que 100 bougies et la requête entière est rejetée d’entrée, pas silencieusement renvoyée sous forme de tableau de 100 null. La réponse liste chaque indicateur que tu as sous-alimenté d’un coup, donc tu corriges tout l’appel en un seul aller-retour:
{
"detail": {
"error": "insufficient_bars",
"message": "insufficient bars: have 30, but: slowSma (type=sma) needs 200, longRsi (type=rsi) needs 51",
"available": 30,
"deficits": [
{ "outputKey": "slowSma", "type": "sma", "required": 200, "available": 30 },
{ "outputKey": "longRsi", "type": "rsi", "required": 51, "available": 30 }
]
}
}Le nombre de bougies requis est calculé par indicateur d’après ses paramètres, ce n’est pas un plancher global. Les sorties SMC partagent une base MIN_BARS (50 par défaut) parce que le pipeline structurel suppose un historique qui a du sens. La surface d’erreur totale, c’est exactement quatre codes: 400 (celui-ci, avec un corps structuré), 413 (au-dessus de MAX_BARS, 5000 par défaut), 422 (échec du schéma Pydantic sur le payload de bougies), 500 (ouvre une issue, ça ne devrait pas arriver).
Il se documente lui-même
GET /metadata renvoie un catalogue statique de chaque indicateur, signal et niveau que l’endpoint de calcul peut émettre, avec des libellés lisibles, des descriptions, des pistes d’interprétation, des unités, des catégories. Récupère-le une fois au démarrage et mets-le en cache, le cache s’invalide au changement de version. Il couvre les ~70 indicateurs de premier niveau du registre, plus les objets imbriqués.
Il y a un helper lookup(path) avec un repli à trois étages, correspondance exacte, puis suppression des indices de tableau, puis feuille nue, donc des chemins pointés dynamiques comme retracements[42].Direction ou prevTimeframe.rsi se résolvent quand même en quelque chose de sensé au lieu de ne rien renvoyer.
Ce qui compte surtout quand le consommateur est un LLM: il peut demander ce que veut dire un champ plutôt que d’halluciner une interprétation de bosChoch[3].level.
Serveur MCP, skill et plugin
La v0.6.0 a ajouté un serveur MCP, donc un modèle à function calling peut passer un jeu de bougies à wickworks et récupérer des primitives sous forme d’appel d’outil. Le repo livre aussi un skill d’agent et un plugin OpenClaw sous .agents/, publiés sur ClawHub par la CI aux pushs de tags.
La position ne change pas juste parce que l’appelant est un LLM: il renvoie toujours des primitives, jamais des signaux. Le modèle reçoit des faits sur le graphique et doit faire son raisonnement lui-même, ce qui est exactement ce que tu veux, parce qu’un modèle qui répète “STRONG BUY” comme un perroquet depuis un service d’indicateurs ne vaut rien.
Comment il se branche au reste
Wickworks est le service d’analyse technique central de la stack psyb0t. mt5-httpapi l’embarque en sidecar verrouillé sur le namespace réseau du container mt5, pas de ports publiés, pas de déploiement séparé. L’API mt5 expose POST /symbols/:symbol/rates/ta: elle récupère les bougies depuis MT5, les transmet à wickworks sous le capot, renvoie le JSON d’indicateurs au client. Un seul appel. Le client n’a jamais à savoir que wickworks existe.
Les backtesters font pareil. Les services d’alerte font pareil. Les scrapers qui doivent calculer des indicateurs en direct sur des données OHLC en flux font pareil. Un container, un jeu de maths, un jeu de tests qui cloue ces maths. Chaque consommateur est un client HTTP maigre qui sait envoyer des bougies et parser la réponse, et la réponse a la même forme peu importe qui demande.
La configuration, c’est quatre variables d’environnement, des défauts sensés, rien d’obligatoire: LOG_LEVEL=INFO, MAX_BARS=5000, MIN_BARS=50, WORKERS=2. Tire l’image, lance le container, pointe tes clients sur :8000.
Sois propriétaire des maths
Le pitch se réduit à une phrase. Arrête de louer des opinions sur tes propres graphiques. Calcule les primitives toi-même, sur ton propre matériel, avec des maths que tu peux auditer et des tests que tu peux lire. Construis la couche d’interprétation dans du code que tu contrôles. Itère dessus aussi vite que tu peux déployer. N’attends plus jamais qu’un salon Discord te dise quand l’order block compte.
Sous licence WTFPL. Fais-en ce que tu veux, putain.
github.com/psyb0t/docker-wickworks · hub.docker.com/r/psyb0t/wickworks
L’Installer Dans Ton Agent
Le skill et le plugin s’installent depuis un marketplace partagé plutôt qu’un repo à la fois. Tout ce qui est sous .agents/ est catalogué dans un seul marketplace, donc ça fait deux commandes:
claude plugin marketplace add psyb0t/agents
claude plugin install wickworks@psyb0tCodex utilise le même marketplace avec un verbe différent, codex plugin add wickworks@psyb0t, parce qu’il n’existe pas de codex plugin install. Il trouve aussi le skill tout seul dans un checkout du repo, puisqu’il scanne .agents/skills/ nativement sans que rien ne soit installé. Il est aussi listé sur le MCP Registry officiel maintenant, donc un client qui résout ses serveurs depuis là peut le trouver sans qu’on lui donne une URL.