Tools: How I Built a Chrome Extension That Injects AI Into Exchange Websites

Tools: How I Built a Chrome Extension That Injects AI Into Exchange Websites

Source: Dev.to

What I Built ## The Technical Parts ## DOM Extraction Instead of APIs ## Market Regime Classification ## Multi-Timeframe Analysis ## Behavioral Tracking ## AI Integration ## What I Learned Building This ## 1. Chrome Extension APIs are Actually Pretty Good ## 2. DOM Extraction is Fragile ## 3. Traders Have Strong Opinions ## 4. Performance Matters More Than I Expected ## Current State I've been trading crypto on and off for a few years. Mostly off after the losses. My biggest problem wasn't the charts. It was me. I'd see a loss, get frustrated, immediately enter another trade to "make it back." Classic revenge trading. Or I'd take a setup on the 15-minute chart while completely ignoring that the daily chart was screaming the opposite direction. Nothing in my trading setup actually warned me before I made these mistakes. The charts just... sit there. They don't say "hey, you just lost, maybe don't trade for the next hour." So I built something that does. LenQuant is a Chrome extension that overlays on top of Binance, Bybit, OKX, and TradingView. It adds a side panel with: And a few other things: wallet tracking across exchanges, watchlist, alerts, journal. One decision I'm pretty happy with: I didn't require API keys for wallet tracking. Most trading tools ask you to paste in your exchange API keys. That's a security nightmare. Users have to trust you not to steal their keys. Exchanges can change API formats and break your integration. Instead, LenQuant reads position data directly from the DOM when users have their exchange tab open. The extension content script finds the elements showing positions, extracts the values, and displays them in the panel. Obviously the real code handles selector differences between exchanges, error cases, etc. But the core idea: no API keys, just read what's already on screen. Tradeoff: Only works when the exchange tab is open. But traders usually have their exchange open anyway, so this hasn't been an issue. I classify market conditions into three buckets: trending, ranging, choppy. Uses a combination of: The "choppy" classification is the money. Most of my losses happened in choppy markets. Knowing when to sit out is more valuable than knowing when to enter. For MTF confluence, I scan four timeframes simultaneously and score how aligned they are. When someone tries to go long on a 15m setup but the 4h and daily are both bearish, they get a warning. Simple idea, but surprisingly effective at preventing bad entries. This is probably the most "creepy" feature, but it's also the most useful. The extension tracks: I know, it sounds obvious. But when you're in the heat of trading and just took a loss, your brain doesn't naturally think "wait, let me cool off." Having software tell you is different from telling yourself. For trade plan generation, I send market context to OpenAI/Anthropic: The response includes entry triggers, stop-loss placement with reasoning, and multiple targets. Important: The AI doesn't trade for you. It generates plans. You still decide whether to execute. This is intentional - I wanted a tool that makes me a better trader, not a bot that trades for me. Manifest V3 gets a lot of hate, but building with it wasn't bad. Service workers for background logic, content scripts for DOM access, side panels for UI. The separation makes sense. The main pain point: debugging service worker crashes. They get terminated when idle, and if you have state that didn't get saved, it's gone. Learned to persist everything to chrome.storage.local. Every exchange styles their pages differently. Binance Futures has different classes than Binance Spot. Bybit recently redesigned their UI and broke my selectors. My approach: I have a selector config per exchange that I update when things break. Users can also report broken extraction, and I can often fix it same-day - I also have different layers of fallback with network detection and on last case with APIs The feedback has been... opinionated. Some people want more indicators. Some want fewer. Some want alerts every time price moves 0.1%. Some never want to see a notification. Current approach: make everything configurable. Don't assume I know what the user wants. Running real-time analysis on multiple symbols while a user is trying to trade means you can't slow down their page. Web Workers for heavy computation, debouncing on DOM observations, lazy loading for components. The extension is live on the Chrome Web Store. Free tier gives you 25 analyses per day, which is enough for most traders. Paid tiers unlock the full feature set. If you trade crypto and want to try it: lenquant.com If you're a dev building Chrome extensions and have questions about anything I mentioned here, happy to answer in comments. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK: // Simplified example of DOM extraction const positions = document.querySelectorAll('.position-row'); positions.forEach(row => { const symbol = row.querySelector('.symbol')?.textContent; const size = row.querySelector('.size')?.textContent; const pnl = row.querySelector('.pnl')?.textContent; // Store and display in panel }); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: // Simplified example of DOM extraction const positions = document.querySelectorAll('.position-row'); positions.forEach(row => { const symbol = row.querySelector('.symbol')?.textContent; const size = row.querySelector('.size')?.textContent; const pnl = row.querySelector('.pnl')?.textContent; // Store and display in panel }); COMMAND_BLOCK: // Simplified example of DOM extraction const positions = document.querySelectorAll('.position-row'); positions.forEach(row => { const symbol = row.querySelector('.symbol')?.textContent; const size = row.querySelector('.size')?.textContent; const pnl = row.querySelector('.pnl')?.textContent; // Store and display in panel }); COMMAND_BLOCK: function classifyRegime(atr, adx, rsiPattern) { if (adx > 25 && atr.increasing) { return 'trending'; } if (adx < 20 && atr.stable) { return 'ranging'; } if (atr.erratic && rsiPattern === 'whipsaw') { return 'choppy'; } return 'uncertain'; } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: function classifyRegime(atr, adx, rsiPattern) { if (adx > 25 && atr.increasing) { return 'trending'; } if (adx < 20 && atr.stable) { return 'ranging'; } if (atr.erratic && rsiPattern === 'whipsaw') { return 'choppy'; } return 'uncertain'; } COMMAND_BLOCK: function classifyRegime(atr, adx, rsiPattern) { if (adx > 25 && atr.increasing) { return 'trending'; } if (adx < 20 && atr.stable) { return 'ranging'; } if (atr.erratic && rsiPattern === 'whipsaw') { return 'choppy'; } return 'uncertain'; } COMMAND_BLOCK: const timeframes = ['15m', '1h', '4h', '1d']; async function calculateConfluence(symbol) { const trends = await Promise.all( timeframes.map(tf => getTrend(symbol, tf)) ); const bullish = trends.filter(t => t === 'bullish').length; const bearish = trends.filter(t => t === 'bearish').length; const confluenceScore = Math.max(bullish, bearish) / timeframes.length * 100; return { score: confluenceScore, direction: bullish > bearish ? 'bullish' : 'bearish', conflicting: bullish > 0 && bearish > 0 }; } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const timeframes = ['15m', '1h', '4h', '1d']; async function calculateConfluence(symbol) { const trends = await Promise.all( timeframes.map(tf => getTrend(symbol, tf)) ); const bullish = trends.filter(t => t === 'bullish').length; const bearish = trends.filter(t => t === 'bearish').length; const confluenceScore = Math.max(bullish, bearish) / timeframes.length * 100; return { score: confluenceScore, direction: bullish > bearish ? 'bullish' : 'bearish', conflicting: bullish > 0 && bearish > 0 }; } COMMAND_BLOCK: const timeframes = ['15m', '1h', '4h', '1d']; async function calculateConfluence(symbol) { const trends = await Promise.all( timeframes.map(tf => getTrend(symbol, tf)) ); const bullish = trends.filter(t => t === 'bullish').length; const bearish = trends.filter(t => t === 'bearish').length; const confluenceScore = Math.max(bullish, bearish) / timeframes.length * 100; return { score: confluenceScore, direction: bullish > bearish ? 'bullish' : 'bearish', conflicting: bullish > 0 && bearish > 0 }; } CODE_BLOCK: function checkRevengeTradingRisk(tradeHistory) { const lastTrade = tradeHistory[tradeHistory.length - 1]; if (!lastTrade) return { risk: 'low' }; const minutesSinceLast = (Date.now() - lastTrade.closedAt) / 60000; const wasLoss = lastTrade.pnl < 0; if (wasLoss && minutesSinceLast < 10) { return { risk: 'high', message: 'You closed a losing trade 10 minutes ago. Take a break.' }; } // ... more checks for overtrading, tilt, etc. } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function checkRevengeTradingRisk(tradeHistory) { const lastTrade = tradeHistory[tradeHistory.length - 1]; if (!lastTrade) return { risk: 'low' }; const minutesSinceLast = (Date.now() - lastTrade.closedAt) / 60000; const wasLoss = lastTrade.pnl < 0; if (wasLoss && minutesSinceLast < 10) { return { risk: 'high', message: 'You closed a losing trade 10 minutes ago. Take a break.' }; } // ... more checks for overtrading, tilt, etc. } CODE_BLOCK: function checkRevengeTradingRisk(tradeHistory) { const lastTrade = tradeHistory[tradeHistory.length - 1]; if (!lastTrade) return { risk: 'low' }; const minutesSinceLast = (Date.now() - lastTrade.closedAt) / 60000; const wasLoss = lastTrade.pnl < 0; if (wasLoss && minutesSinceLast < 10) { return { risk: 'high', message: 'You closed a losing trade 10 minutes ago. Take a break.' }; } // ... more checks for overtrading, tilt, etc. } CODE_BLOCK: async function generateTradePlan(symbol, context) { const prompt = ` You are a professional trader analyzing ${symbol}. Current price: ${context.price} Regime: ${context.regime} MTF Confluence: ${context.confluence}% Key levels: ${context.levels.join(', ')} Generate a trade plan with: - Entry trigger - Stop-loss with reasoning - Three targets with reasoning - Confidence score (0-100) - Key risks `; const response = await callLLM(prompt); return parseTradePlan(response); } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: async function generateTradePlan(symbol, context) { const prompt = ` You are a professional trader analyzing ${symbol}. Current price: ${context.price} Regime: ${context.regime} MTF Confluence: ${context.confluence}% Key levels: ${context.levels.join(', ')} Generate a trade plan with: - Entry trigger - Stop-loss with reasoning - Three targets with reasoning - Confidence score (0-100) - Key risks `; const response = await callLLM(prompt); return parseTradePlan(response); } CODE_BLOCK: async function generateTradePlan(symbol, context) { const prompt = ` You are a professional trader analyzing ${symbol}. Current price: ${context.price} Regime: ${context.regime} MTF Confluence: ${context.confluence}% Key levels: ${context.levels.join(', ')} Generate a trade plan with: - Entry trigger - Stop-loss with reasoning - Three targets with reasoning - Confidence score (0-100) - Key risks `; const response = await callLLM(prompt); return parseTradePlan(response); } - Market regime detection - Is this a trending market or choppy garbage? - Multi-timeframe analysis - Do 15m, 1h, 4h, Daily agree or conflict? - Behavioral tracking - How long since your last trade? Was it a loss? - AI trade planning - Entry, stop, targets generated from market context - ATR (Average True Range) - Volatility measurement - ADX (Average Directional Index) - Trend strength - RSI patterns - Momentum behavior - Time since last trade - Whether last trade was a win or loss - How many trades today - Pattern of trades (rapid fire? evenly spaced?) - Current price and recent price action - Support/resistance levels - Regime classification - MTF confluence data - Recent news (if applicable)