Hyperscalper est un terminal de trading crypto professionnel que j'ai construit pour Hyperliquid DEX. Ce qui le rend unique, c'est qu'il est 100% côté client - pas de serveurs backend, pas d'intermédiaires. Tes clés privées ne quittent jamais ton navigateur.

Dans cet article, je vais passer en revue les décisions architecturales clés, les différentes méthodes de passage d'ordres que j'ai implémentées, et comment fonctionnent les scanners de marché en temps réel.
Pourquoi 100% côté client ?
Quand tu construis une application de trading qui gère des clés privées, la confiance est primordiale. J'ai pris une décision délibérée : zéro backend.
Le problème de confiance
Les plateformes de trading traditionnelles te demandent de leur confier tes identifiants. Même avec les meilleures pratiques de sécurité, il y a toujours un serveur qui pourrait être compromis, une base de données qui pourrait être piratée, ou un employé qui pourrait mal tourner.
Avec Hyperscalper, le flux de données est simple :
Navigateur → API Hyperliquid DEX
C'est tout. Pas de serveurs relais, pas de proxies, pas de bases de données backend.
Comment les clés privées sont sécurisées
Même si tout tourne dans le navigateur, j'avais besoin d'un chiffrement robuste pour la clé privée stockée dans le localStorage. Voici l'approche :
// AES-GCM encryption with PBKDF2 key derivationConst deriveKey = async (password: string, salt: Uint8Array) => {const keyMaterial = await crypto.subtle.importKey('raw',new TextEncoder().encode(password),'PBKDF2',false,['deriveBits', 'deriveKey']);return crypto.subtle.deriveKey({name: 'PBKDF2',salt,iterations: 100000, // Makes brute-force expensivehash: 'SHA-256'},keyMaterial,{ name: 'AES-GCM', length: 256 },false,['encrypt', 'decrypt']);};
Le système génère une clé spécifique à l'appareil au premier lancement, stockée dans le localStorage. Les utilisateurs peuvent optionnellement ajouter un mot de passe pour une protection supplémentaire. Même si quelqu'un vole les données du localStorage, il faudrait 100 000 itérations PBKDF2 pour tester chaque tentative.
Les compromis
Aller 100% côté client a des conséquences :
- Zéro confiance requise — mais impossible de faire tourner des bots côté serveur
- Aucune collecte de données — mais l'historique des ordres dépend de l'API de l'exchange
- Vraie décentralisation — mais tout le calcul se fait dans le navigateur
- Zéro coût serveur — mais impossible de faire du backtesting lourd
Pour un terminal de scalping où la vitesse compte, ces compromis font sens. La latence entre ton navigateur et Hyperliquid est tout ce qui compte.
Méthodes de passage d'ordres
Les traders ont des workflows différents. Certains préfèrent cliquer, d'autres utilisent des raccourcis clavier, et certains veulent des ordres en échelle automatisés. J'ai implémenté cinq façons distinctes de passer des ordres.
Ordres par clic sur le graphique
Parfois tu veux placer un ordre à un niveau de prix spécifique que tu vois sur le graphique. J'ai implémenté un système de réticule qui rend ça intuitif :

Const handleChartClick = (params) => {if (!crosshairActive) return;const price = params.price;const isBuy = price < currentPrice;placeLimitOrderAtPrice({symbol,price,isBuy,percentage: settings.orderSizePercent});setCrosshairActive(false);};
Clic en dessous du prix actuel = ordre d'achat limit. Clic au-dessus = ordre de vente limit. Simple et intuitif.
Ordres Cloud Ladder (DCA)
La fonctionnalité signature. Au lieu de placer un seul ordre, les ordres Cloud placent 5 ordres limit empilés à intervalles en dessous (pour les achats) ou au-dessus (pour les ventes) du prix actuel.
Const buyCloud = async ({ symbol, currentPrice, priceInterval, percentage }) => {const ORDER_COUNT = 5;const TAKE_PROFIT_PERCENT = 2;const orders = [];for (let I = 0; I < ORDER_COUNT; I++) {const price = currentPrice - (priceInterval * I);const size = totalSize / ORDER_COUNT;orders.push({type: 'limit',side: 'buy',price,size,reduceOnly: false});// Auto TP at 2% profitorders.push({type: 'limit',side: 'sell',price: price * (1 + TAKE_PROFIT_PERCENT / 100),size,reduceOnly: true});}await Promise.all(orders.map(o => submitOrder(o)));};
L'intervalle de prix est calculé à partir de la hauteur des bougies récentes - le système s'adapte à la volatilité actuelle.
Raccourcis clavier
Pour les speed traders, attraper la souris c'est trop lent. J'ai mappé toutes les actions courantes sur des hotkeys :

UseEffect(() => {const handleKeyDown = (e: KeyboardEvent) => {if (e.metaKey || e.ctrlKey) {switch (e.key) {case 'b':e.preventDefault();inverted ? SellCloud() : buyCloud();break;case 's':e.preventDefault();inverted ? BuyCloud() : sellCloud();break;case 'e':e.preventDefault();closePosition(25); // Close 25%break;}}};window.addEventListener('keydown', handleKeyDown);return () => window.removeEventListener('keydown', handleKeyDown);}, [inverted]);
Note le check inverted - on en reparle plus bas.
Mises à jour optimistes
Personne ne veut attendre 500ms la réponse de l'API avant de voir un feedback. J'ai implémenté des mises à jour optimistes :
Const placeOrder = async (order) => {// 1. Afficher immédiatement dans l'UIconst optimisticId = crypto.randomUUID();addOptimisticOrder({ ...order, id: optimisticId });showToast('Placing order...');try {// 2. Soumettre réellement à l'exchangeconst result = await submitToHyperliquid(order);// 3. Remplacer l'optimiste par le réelremoveOptimisticOrder(optimisticId);addConfirmedOrder(result);playSuccessSound();} catch (error) {// 4. Rollback en cas d'échecremoveOptimisticOrder(optimisticId);showErrorToast(error.message);}};
L'UI répond en moins de 100ms quelle que soit la latence réseau.
Scanners de marché
Un terminal de trading n'est aussi bon que sa capacité à trouver des opportunités. J'ai construit 8 types de scanners différents qui tournent en temps réel sur tous les symboles.

Architecture des scanners
Chaque scanner s'abonne aux données de bougies via WebSocket et lance une analyse à chaque mise à jour :
// Shared WebSocket connection (singleton pattern)Const ws = getWebSocketService();// Subscribe to all symbols on multiple timeframesSymbols.forEach(symbol => {['1m', '5m', '15m', '1h'].forEach(timeframe => {ws.subscribeToCandles(symbol, timeframe, (candles) => {runScanners(symbol, timeframe, candles);});});});
Scanner stochastique
L'oscillateur stochastique est excellent pour repérer les conditions de survente/surachat. J'ai implémenté quatre variantes avec des périodes différentes :
Const scanStochastic = (candles, config) => {const variants = {ultraFast: { period: 5, smoothK: 2, smoothD: 2 },fast: { period: 9, smoothK: 3, smoothD: 3 },medium: { period: 14, smoothK: 3, smoothD: 3 },slow: { period: 21, smoothK: 5, smoothD: 5 }};const signals = [];Object.entries(variants).forEach(([name, params]) => {if (!config.variants[name].enabled) return;const stoch = calculateStochastic(candles, params);const current = stoch[stoch.length - 1];const previous = stoch[stoch.length - 2];// Bullish: K crosses above D in oversold zoneif (current.k > current.d &&previous.k <= previous.d &¤t.k < config.oversoldLevel) {signals.push({type: 'stochastic',variant: name,direction: 'bullish',strength: (config.oversoldLevel - current.k) / config.oversoldLevel});}});return signals;};
Détection de divergences
Les divergences sont des signaux puissants - quand le prix fait un nouveau plus bas mais que le RSI fait un plus bas plus haut, ça signale souvent un retournement. Ça nécessitait une logique plus complexe :
Const detectDivergence = (candles, rsiValues) => {const pricePivots = findPivots(candles, 3);const rsiPivots = findPivots(rsiValues, 3);const divergences = [];const recentPriceLows = pricePivots.filter(p => p.type === 'low').slice(-5);for (let I = 1; I < recentPriceLows.length; I++) {const current = recentPriceLows[I];const previous = recentPriceLows[I - 1];// Price made lower lowif (current.price < previous.price) {const currentRsi = findClosestPivot(rsiPivots, current.index);const previousRsi = findClosestPivot(rsiPivots, previous.index);// RSI made higher low = bullish divergenceif (currentRsi.value > previousRsi.value) {divergences.push({type: 'regular_bullish',pricePoints: [previous, current],rsiPoints: [previousRsi, currentRsi],strength: calculateDivergenceStrength(...)});}}}return divergences;};
Considérations de performance
Faire tourner 8 scanners sur 500+ symboles sur 4 timeframes pourrait facilement tuer les performances du navigateur. J'ai utilisé plusieurs techniques d'optimisation :
Mémoïsation avec TTL :
Const memoizedCalculate = createMemoizedFunction(calculateStochastic,(candles, period) => `stoch-${candles.length}-${period}`,100, // max cache entries60000 // expire after 60s);
Détection de divergences avec debounce :
// Don't recalculate on every tickConst debouncedDivergence = useDebouncedCallback(detectDivergence,1000 // Wait 1s after last update);
Virtual scrolling pour les résultats :
<FixedSizeListheight={600}itemCount={signals.length}itemSize={60}>{({ index, style }) => (<SignalItem signal={signals[index]} style={style} />)}</FixedSizeList>
Analyse multi-timeframe
Une des fonctionnalités les plus puissantes est la vue multi-timeframe. Voir le même symbole sur 1m, 5m, 15m et 1h simultanément aide à identifier les confluences.

Tous les graphiques sont synchronisés - quand tu zoomes ou défiles sur un graphique, tous les autres suivent. C'est réalisé grâce à un store partagé de synchronisation des graphiques :
Const useChartSyncStore = create((set) => ({timeRange: null,setTimeRange: (range) => set({ timeRange: range }),}));// In each chart componentUseEffect(() => {const unsubscribe = useChartSyncStore.subscribe((state) => state.timeRange,(range) => {if (range) chart.timeScale().setVisibleRange(range);});return unsubscribe;}, [chart]);
Lignes de support et résistance
La détection automatisée de lignes de tendance est une de ces fonctionnalités qui paraissent simples mais qui ont une profondeur surprenante. J'ai implémenté trois approches complémentaires.
Lignes de tendance basées sur les pivots
L'approche la plus intuitive : trouver les points pivots et tracer des lignes à travers.
Const detectPivots = (candles, strength = 3) => {const pivots = [];for (let I = strength; I < candles.length - strength; I++) {const current = candles[I];let isHigh = true;for (let j = I - strength; j <= I + strength; j++) {if (j !== I && candles[j].high >= current.high) {isHigh = false;break;}}if (isHigh) {pivots.push({ index: I, price: current.high, type: 'high' });}// Same logic for lows...}return pivots;};
Validation par enveloppe
Une ligne de support qui se fait violer n'est pas utile. Je valide les lignes par rapport à toutes les bougies :
Const findBestSupportLine = (pivots, candles) => {let bestLine = null;let bestScore = -Infinity;for (let I = 0; I < pivots.length - 1; I++) {for (let j = I + 1; j < pivots.length; j++) {const line = createLine(pivots[I], pivots[j]);let touches = 0;let violations = 0;candles.forEach((candle, idx) => {const linePrice = getLineValueAt(line, idx);const tolerance = linePrice * 0.002;if (candle.low < linePrice - tolerance) {violations++;} else if (Math.abs(candle.low - linePrice) < tolerance) {touches++;}});const score = touches * 10 - violations * 100;if (score > bestScore && violations === 0 && touches >= 3) {bestScore = score;bestLine = line;}}}return bestLine;};
L'insight clé : une ligne de support avec zéro violation a beaucoup plus de valeur qu'une avec plein de touches mais quelques violations.
Analytique de trading
Suivre sa performance est essentiel pour progresser en tant que trader. Hyperscalper fournit des vues P&L journalières et mensuelles :


Toutes ces données viennent directement de l'API d'Hyperliquid - aucun stockage backend nécessaire.
Le mode inversé
Voilà une fonctionnalité née de l'expérience réelle de trading : le mode inversé.
Quand tu es un trader orienté short, l'UI standard est déroutante. Le vert signifie que le prix a monté (mauvais pour les shorts), le rouge signifie qu'il a baissé (bon pour les shorts). Les signaux haussiers sont des opportunités baissières.
Le mode inversé retourne tout :
Const getSignalColor = (signal, inverted) => {const isBullish = signal.direction === 'bullish';if (inverted) {// Bullish signal = shorting opportunity = show as "good" (green)return isBullish ? Colors.bearish : colors.bullish;}return isBullish ? Colors.bullish : colors.bearish;};
Les couleurs des bougies, les indicateurs de signaux, même la sémantique des labels support/résistance s'inversent. Un trader short voit les mêmes patterns qu'un trader long, juste interprétés correctement pour son biais.
Support multi-écrans
Les traders professionnels utilisent souvent plusieurs écrans. Hyperscalper supporte le détachement de graphiques dans des fenêtres séparées :

Chaque fenêtre popup maintient sa propre connexion WebSocket et synchronise son état avec la fenêtre principale via BroadcastChannel :
Const channel = new BroadcastChannel('hyperscalper-sync');// Main window broadcasts state changesChannel.postMessage({ type: 'POSITION_UPDATE', data: positions });// Popup windows listenChannel.onmessage = (event) => {if (event.data.type === 'POSITION_UPDATE') {setPositions(event.data.data);}};
Gestion d'état avec Zustand
Avec plus de 20 stores gérant différents domaines, j'avais besoin d'une gestion d'état légère. Redux paraissait overkill. J'ai choisi Zustand :
Const useTradingStore = create<TradingStore>((set, get) => ({orders: [],positions: [],addOrder: (order) => set((state) => ({orders: [...state.orders, order]})),closePosition: async (symbol, percentage) => {const position = get().positions.find(p => p.symbol === symbol);if (!position) return;const size = position.size * (percentage / 100);await submitMarketOrder({symbol,side: position.side === 'long' ? 'sell' : 'buy',size,reduceOnly: true});}}));
Chaque store est focalisé sur un seul domaine : trading, ordres, positions, bougies, scanner, paramètres, etc. Les composants souscrivent exactement à ce dont ils ont besoin.
Leçons apprises
Construire Hyperscalper m'a appris plusieurs choses :
1. Le côté client peut suffire. Pour les applications où la confiance compte, éliminer le backend n'est pas juste possible - c'est préférable.
2. Les mises à jour optimistes sont essentielles. En trading, 500ms de latence ça ressemble à une éternité. Montre le feedback immédiatement, réconcilie après.
3. Les méthodes d'input multiples comptent. Différents traders ont différents workflows. Certains cliquent, certains utilisent des hotkeys, certains veulent de l'automatisation. Il faut tous les supporter.
4. L'optimisation de performance est obligatoire. Faire tourner de l'analyse technique sur 500 symboles en temps réel demande une attention particulière à la mémoïsation, au debouncing et au virtual rendering.
5. Penser à l'envers aide. Construire pour les traders short m'a forcé à questionner les hypothèses sur ce que « hausse » et « bien » veulent dire. Le résultat est un système plus flexible.
Hyperscalper est live sur hyperscalper.vercel.app si tu veux l'essayer. Le code démontre que des outils de trading de niveau professionnel peuvent tourner entièrement dans le navigateur - aucun backend requis.
Stay Updated
Get notified about new posts on automation, productivity tips, indie hacking, and web3.
No spam, ever. Unsubscribe anytime.



