├── README.md ├── index.html ├── script.js └── styles.css /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shidiqxyz/bulldiv-binance-web/cc5740528d46624bc9de9c5692157b989bba7954/README.md -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bullish Divergence Detector 7 | 8 | 9 | 10 | 11 |
12 |
13 |

Binance Bullish Divergence Detector

14 |
15 | 16 | 23 | 24 |
25 |
26 |

Ticker with Bullish Divergence:

27 |
    28 |
    29 |
    30 |
    31 |

    Bybit Bullish Divergence Detector

    32 |
    33 | 34 | 41 | 42 |
    43 |
    44 |

    Ticker with Bullish Divergence:

    45 |
      46 |
      47 |
      48 |
      49 |
      50 |

      Developed by Shidiq Twitter and GitHub

      51 |
      52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | document.getElementById('binanceAnalyzeButton').addEventListener('click', async () => { 2 | await analyzeMarket('binance', 'binanceTimeframe', 'binanceSymbolsList'); 3 | }); 4 | 5 | document.getElementById('bybitAnalyzeButton').addEventListener('click', async () => { 6 | await analyzeMarket('bybit', 'bybitTimeframe', 'bybitSymbolsList'); 7 | }); 8 | 9 | async function analyzeMarket(exchangeName, timeframeId, symbolsListId) { 10 | const timeframe = document.getElementById(timeframeId).value; 11 | const symbolsList = document.getElementById(symbolsListId); 12 | symbolsList.innerHTML = ''; 13 | 14 | // Show loading spinner 15 | const spinner = document.createElement('div'); 16 | spinner.className = 'spinner'; 17 | const loadingText = document.createElement('p'); 18 | loadingText.className = 'loading-text'; 19 | loadingText.textContent = 'Loading...'; 20 | 21 | symbolsList.appendChild(spinner); 22 | symbolsList.appendChild(loadingText); 23 | 24 | try { 25 | const exchange = new ccxt[exchangeName](); 26 | const markets = await exchange.loadMarkets(); 27 | const symbols = Object.keys(markets).filter(symbol => symbol.endsWith('/USDT')); 28 | 29 | for (const symbol of symbols) { 30 | try { 31 | const ohlcv = await exchange.fetchOHLCV(symbol, timeframe, undefined, 500); 32 | const df = ohlcv.map(ohlcv => ({ 33 | timestamp: new Date(ohlcv[0]), 34 | open: ohlcv[1], 35 | high: ohlcv[2], 36 | low: ohlcv[3], 37 | close: ohlcv[4], 38 | volume: ohlcv[5], 39 | })); 40 | 41 | const closes = df.map(d => d.close); 42 | const macd = calculateMACD(closes); 43 | const rsi = calculateRSI(closes); 44 | 45 | if (detectBullishDivergence(macd, rsi, closes)) { 46 | const li = document.createElement('li'); 47 | li.textContent = symbol; 48 | symbolsList.appendChild(li); 49 | } 50 | } catch (error) { 51 | console.error(`Could not analyze ${symbol} on ${exchangeName}:`, error); 52 | } 53 | } 54 | 55 | // Remove loading spinner and text after processing 56 | symbolsList.removeChild(spinner); 57 | symbolsList.removeChild(loadingText); 58 | 59 | } catch (error) { 60 | console.error(`Error loading markets for ${exchangeName}:`, error); 61 | // Optionally handle error display here 62 | symbolsList.innerHTML = '

      Error loading data. Please try again.

      '; 63 | } 64 | } 65 | 66 | function calculateMACD(closes) { 67 | const shortEMA = calculateEMA(closes, 12); 68 | const longEMA = calculateEMA(closes, 26); 69 | const macd = shortEMA.map((val, idx) => val - longEMA[idx]); 70 | const signal = calculateEMA(macd.slice(26), 9); 71 | const histogram = macd.slice(26).map((val, idx) => val - signal[idx]); 72 | return { macd: macd.slice(26), signal, histogram }; 73 | } 74 | 75 | function calculateEMA(data, window) { 76 | const k = 2 / (window + 1); 77 | const emaArray = [data[0]]; 78 | for (let i = 1; i < data.length; i++) { 79 | emaArray.push(data[i] * k + emaArray[i - 1] * (1 - k)); 80 | } 81 | return emaArray; 82 | } 83 | 84 | function calculateRSI(closes, period = 14) { 85 | const gains = []; 86 | const losses = []; 87 | for (let i = 1; i < closes.length; i++) { 88 | const diff = closes[i] - closes[i - 1]; 89 | if (diff >= 0) { 90 | gains.push(diff); 91 | losses.push(0); 92 | } else { 93 | gains.push(0); 94 | losses.push(-diff); 95 | } 96 | } 97 | const avgGain = average(gains.slice(0, period)); 98 | const avgLoss = average(losses.slice(0, period)); 99 | const rs = avgGain / avgLoss; 100 | const rsiArray = [100 - 100 / (1 + rs)]; 101 | 102 | for (let i = period; i < gains.length; i++) { 103 | const gain = gains[i]; 104 | const loss = losses[i]; 105 | const newAvgGain = (avgGain * (period - 1) + gain) / period; 106 | const newAvgLoss = (avgLoss * (period - 1) + loss) / period; 107 | const newRS = newAvgGain / newAvgLoss; 108 | rsiArray.push(100 - 100 / (1 + newRS)); 109 | } 110 | return rsiArray; 111 | } 112 | 113 | function average(data) { 114 | return data.reduce((a, b) => a + b, 0) / data.length; 115 | } 116 | 117 | function detectBullishDivergence(macd, rsi, closes) { 118 | const macdLows = macd.histogram.filter(value => value < 0); 119 | const priceLows = closes.slice(-macdLows.length).filter((close, index) => macd.histogram[index] < 0); 120 | 121 | const bullishDivergenceMACD = macdLows.length >= 2 && 122 | priceLows[priceLows.length - 1] > priceLows[priceLows.length - 2] && 123 | macdLows[macdLows.length - 1] < macdLows[macdLows.length - 2]; 124 | 125 | const rsiLows = rsi.filter(value => value < 30); 126 | const bullishDivergenceRSI = rsiLows.length >= 2 && 127 | closes[closes.length - 1] > closes[closes.length - rsiLows.length] && 128 | rsiLows[rsiLows.length - 1] < rsiLows[rsiLows.length - 2]; 129 | 130 | return bullishDivergenceMACD && bullishDivergenceRSI; 131 | } 132 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | background-color: #f4f7f6; 4 | color: #333; 5 | margin: 0; 6 | padding: 0; 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | height: 100vh; 12 | box-sizing: border-box; 13 | } 14 | 15 | .container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | justify-content: space-around; 19 | width: 90%; 20 | max-width: 1200px; 21 | } 22 | 23 | .exchange-container { 24 | background-color: #ffffff; 25 | border-radius: 10px; 26 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 27 | padding: 20px; 28 | width: 45%; 29 | margin: 10px; 30 | text-align: center; 31 | box-sizing: border-box; 32 | } 33 | 34 | h1 { 35 | color: #4CAF50; 36 | font-size: 24px; 37 | } 38 | 39 | .form-group { 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | margin: 20px 0; 44 | } 45 | 46 | label { 47 | margin-right: 10px; 48 | font-size: 16px; 49 | color: #666; 50 | } 51 | 52 | select { 53 | padding: 10px; 54 | border-radius: 5px; 55 | border: 1px solid #ccc; 56 | font-size: 16px; 57 | margin-right: 10px; 58 | } 59 | 60 | button { 61 | background-color: #4CAF50; 62 | color: white; 63 | padding: 10px 20px; 64 | border: none; 65 | border-radius: 5px; 66 | cursor: pointer; 67 | font-size: 16px; 68 | } 69 | 70 | button:hover { 71 | background-color: #45a049; 72 | } 73 | 74 | #result { 75 | margin-top: 20px; 76 | } 77 | 78 | h2 { 79 | color: #333; 80 | font-size: 20px; 81 | margin-bottom: 10px; 82 | } 83 | 84 | ul { 85 | list-style-type: none; 86 | padding: 0; 87 | } 88 | 89 | li { 90 | background-color: #f1f1f1; 91 | margin: 5px 0; 92 | padding: 10px; 93 | border-radius: 5px; 94 | } 95 | 96 | .spinner { 97 | border: 4px solid #f3f3f3; 98 | border-radius: 50%; 99 | border-top: 4px solid #3498db; 100 | width: 40px; 101 | height: 40px; 102 | animation: spin 1s linear infinite; 103 | margin: 20px auto; 104 | } 105 | 106 | @keyframes spin { 107 | 0% { transform: rotate(0deg); } 108 | 100% { transform: rotate(360deg); } 109 | } 110 | 111 | .loading-text { 112 | font-size: 14px; 113 | color: #888; 114 | margin-top: 10px; 115 | } 116 | 117 | /* Credits */ 118 | #credits { 119 | text-align: center; 120 | margin-top: 20px; 121 | } 122 | 123 | #credits p { 124 | font-size: 18px; 125 | color: #666; 126 | } 127 | 128 | #credits a { 129 | color: #4CAF50; 130 | text-decoration: none; 131 | } 132 | 133 | #credits a:hover { 134 | text-decoration: underline; 135 | } 136 | 137 | /* Responsive design */ 138 | @media (max-width: 768px) { 139 | body { 140 | height: auto; 141 | } 142 | 143 | .container { 144 | flex-direction: column; 145 | align-items: center; 146 | } 147 | 148 | .exchange-container { 149 | width: 90%; 150 | margin: 10px 0; 151 | } 152 | 153 | .form-group { 154 | flex-direction: column; 155 | } 156 | 157 | label, select, button { 158 | margin: 5px 0; 159 | } 160 | } 161 | --------------------------------------------------------------------------------