Hyperscalper es un terminal profesional de trading de criptomonedas que construí para Hyperliquid DEX. Lo que lo hace único es que es 100% del lado del cliente - sin servidores backend, sin intermediarios. Tus claves privadas nunca salen de tu navegador.

En este post voy a recorrer las decisiones arquitectónicas clave, los diferentes métodos de entrada de órdenes que implementé, y cómo funcionan los escáneres de mercado en tiempo real.
¿Por Qué 100% del Lado del Cliente?
Cuando construyes una aplicación de trading que maneja claves privadas, la confianza lo es todo. Tomé una decisión deliberada: cero backend.
El Problema de la Confianza
Las plataformas de trading tradicionales requieren que confíes en ellas con tus credenciales. Incluso con las mejores prácticas de seguridad, siempre hay un servidor que podría ser comprometido, una base de datos que podría ser vulnerada, o un empleado que podría volverse deshonesto.
Con Hyperscalper, el flujo de datos es simple:
Browser → Hyperliquid DEX API
Eso es todo. Sin servidores relay, sin proxies, sin bases de datos backend.
Cómo se Aseguran las Claves Privadas
Aunque todo corre en el navegador, igual necesitaba encriptación robusta para la clave privada almacenada en localStorage. Este es el enfoque:
// 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']);};
El sistema genera una clave específica del dispositivo en el primer uso, almacenada en localStorage. Los usuarios pueden opcionalmente agregar una contraseña para protección adicional. Incluso si alguien roba los datos de localStorage, necesitaría 100,000 iteraciones de PBKDF2 para crackear cada intento.
Los Trade-offs
Ir 100% del lado del cliente tiene consecuencias:
- Cero confianza requerida — pero no puedes correr bots del lado del servidor
- Sin recolección de datos — pero el historial de órdenes depende de la API del exchange
- Verdadera descentralización — pero toda la computación ocurre en el navegador
- Sin costos de servidor — pero no puedes hacer backtesting pesado
Para un terminal de scalping donde la velocidad importa, estos trade-offs tienen sentido. La latencia entre tu navegador y Hyperliquid es todo lo que importa.
Métodos de Entrada de Órdenes
Los traders tienen diferentes flujos de trabajo. Algunos prefieren hacer clic, otros usan hotkeys, y algunos quieren órdenes ladder automatizadas. Implementé cinco formas distintas de ingresar órdenes.
Órdenes Click-on-Chart
A veces quieres colocar una orden a un nivel de precio específico que ves en el gráfico. Implementé un sistema de crosshair que hace esto intuitivo:

Const handleChartClick = (params) => {if (!crosshairActive) return;const price = params.price;const isBuy = price < currentPrice;placeLimitOrderAtPrice({symbol,price,isBuy,percentage: settings.orderSizePercent});setCrosshairActive(false);};
Clic debajo del precio actual = orden límite de compra. Clic arriba = orden límite de venta. Simple e intuitivo.
Cloud Ladder Orders (DCA)
La función estrella. En lugar de colocar una sola orden, las Cloud orders colocan 5 órdenes límite apiladas a intervalos por debajo (para compras) o por encima (para ventas) del precio actual.
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)));};
El intervalo de precio se calcula a partir de las alturas de las velas recientes - el sistema se adapta a la volatilidad actual.
Atajos de Teclado
Para traders de velocidad, mover la mano al ratón es demasiado lento. Mapeé todas las acciones comunes a 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]);
Fíjate en el check de inverted - más sobre eso después.
Actualizaciones Optimistas
Nadie quiere esperar 500ms por una respuesta de API antes de ver feedback. Implementé actualizaciones optimistas:
Const placeOrder = async (order) => {// 1. Immediately show in UIconst optimisticId = crypto.randomUUID();addOptimisticOrder({ ...order, id: optimisticId });showToast('Placing order...');try {// 2. Actually submit to exchangeconst result = await submitToHyperliquid(order);// 3. Replace optimistic with realremoveOptimisticOrder(optimisticId);addConfirmedOrder(result);playSuccessSound();} catch (error) {// 4. Rollback on failureremoveOptimisticOrder(optimisticId);showErrorToast(error.message);}};
La UI responde en menos de 100ms sin importar la latencia de red.
Escáneres de Mercado
Un terminal de trading es tan bueno como su capacidad para encontrar oportunidades. Construí 8 tipos diferentes de escáneres que corren en tiempo real a través de todos los símbolos.

Arquitectura de los Escáneres
Cada escáner se suscribe a datos de velas vía WebSocket y ejecuta análisis en cada actualización:
// 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);});});});
Escáner Estocástico
El oscilador estocástico es genial para encontrar condiciones de sobreventa/sobrecompra. Implementé cuatro variantes con diferentes períodos:
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;};
Detección de Divergencias
Las divergencias son señales poderosas - cuando el precio hace un nuevo mínimo pero el RSI hace un mínimo más alto, frecuentemente señala una reversión. Esto requirió lógica más compleja:
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;};
Consideraciones de Rendimiento
Correr 8 escáneres a través de 500+ símbolos en 4 timeframes podría fácilmente matar el rendimiento del navegador. Usé varias técnicas de optimización:
Memoización con TTL:
Const memoizedCalculate = createMemoizedFunction(calculateStochastic,(candles, period) => `stoch-${candles.length}-${period}`,100, // max cache entries60000 // expire after 60s);
Detección de divergencias con debounce:
// Don't recalculate on every tickConst debouncedDivergence = useDebouncedCallback(detectDivergence,1000 // Wait 1s after last update);
Virtual scrolling para resultados:
<FixedSizeListheight={600}itemCount={signals.length}itemSize={60}>{({ index, style }) => (<SignalItem signal={signals[index]} style={style} />)}</FixedSizeList>
Análisis Multi-Temporal
Una de las funciones más potentes es la vista multi-temporal. Ver el mismo símbolo en 1m, 5m, 15m y 1h simultáneamente ayuda a identificar confluencia.

Todos los gráficos están sincronizados - cuando haces zoom o paneas un gráfico, todos los demás siguen. Esto se logra con un store de sincronización compartido:
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]);
Líneas de Soporte y Resistencia
La detección automatizada de líneas de tendencia es una de esas funciones que suena simple pero tiene una profundidad sorprendente. Implementé tres enfoques complementarios.
Líneas de Tendencia Basadas en Pivotes
El enfoque más intuitivo: encontrar puntos pivote y trazar líneas a través de ellos.
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;};
Validación por Envolvente
Una línea de soporte que se viola no es útil. Valido las líneas contra todas las velas:
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;};
La clave: una línea de soporte con cero violaciones es mucho más valiosa que una con muchos toques pero algunas violaciones.
Analíticas de Trading
Hacer seguimiento del rendimiento es esencial para mejorar como trader. Hyperscalper provee resúmenes diarios y mensuales de P&L:


Todos estos datos vienen directamente de la API de Hyperliquid - sin necesidad de almacenamiento backend.
El Modo Invertido
Aquí va una función nacida de la experiencia real de trading: modo invertido.
Cuando eres un trader con sesgo bajista, la UI estándar es confusa. Verde significa que el precio subió (malo para shorts), rojo significa que bajó (bueno para shorts). Las señales alcistas son oportunidades bajistas.
El modo invertido voltea todo:
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;};
Colores de velas, indicadores de señales, incluso la semántica de las etiquetas de soporte/resistencia se invierten. Un trader de shorts ve los mismos patrones que un trader de longs, solo interpretados correctamente para su sesgo.
Soporte Multi-Monitor
Los traders profesionales frecuentemente usan múltiples monitores. Hyperscalper soporta separar gráficos en ventanas independientes:

Cada ventana emergente mantiene su propia conexión WebSocket y sincroniza estado con la ventana principal vía 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);}};
Manejo de Estado con Zustand
Con 20+ stores manejando diferentes dominios, necesitaba manejo de estado liviano. Redux se sentía excesivo. Elegí 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});}}));
Cada store se enfoca en un dominio: trading, órdenes, posiciones, velas, escáner, configuración, etc. Los componentes se suscriben exactamente a lo que necesitan.
Lecciones Aprendidas
Construir Hyperscalper me enseñó varias cosas:
1. Del lado del cliente puede ser suficiente. Para aplicaciones donde la confianza importa, eliminar el backend no solo es posible - es preferible.
2. Las actualizaciones optimistas son esenciales. En trading, 500ms de latencia se sienten como una eternidad. Muestra feedback inmediatamente, reconcilia después.
3. Múltiples métodos de entrada importan. Diferentes traders tienen diferentes flujos de trabajo. Algunos hacen clic, otros usan hotkeys, algunos quieren automatización. Soporta a todos.
4. La optimización de rendimiento es obligatoria. Correr análisis técnico en 500 símbolos en tiempo real requiere atención cuidadosa a memoización, debouncing y renderizado virtual.
5. Pensar invertido ayuda. Construir para traders de shorts me obligó a cuestionar suposiciones sobre qué significan "arriba" y "bueno". El resultado es un sistema más flexible.
Hyperscalper está en vivo en hyperscalper.vercel.app si quieres probarlo. El código demuestra que herramientas de trading de nivel profesional pueden correr enteramente en el navegador - sin backend requerido.
Stay Updated
Get notified about new posts on automation, productivity tips, indie hacking, and web3.
No spam, ever. Unsubscribe anytime.



