Saturday, June 28, 2025

crypto analizer


<!DOCTYPE html>
<html>
<head>
  <title>Input Field Example</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/chartjs-chart-financial"></script>
  <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
  <style>
    body {
      font-family: 'JetBrains Mono', monospace, 'Trebuchet MS', 'Lucida Grande', Verdana, Arial, Helvetica, sans-serif;
      background-color: #121212;
      color: #e0e0e0;
      margin: 0;
      padding: 20px;
      line-height: 1.5;
    }
    .container {
      width: 350px;
      margin: 40px auto;
      text-align: left; /* Override the default center alignment */
    }
    /* Chart container styles */
    .chart-container {
      width: 100%;
      height: 200px;
      margin-bottom: 20px;
      border: 1px solid #333;
      border-radius: 4px;
      overflow: hidden;
      background-color: #1a1a1a;
    }
    .chart-controls {
      display: flex;
      justify-content: center;
      align-items: center;
      margin-bottom: 10px;
    }
    .input-container {
      display: flex;
      margin-bottom: 10px;
      position: relative; /* For positioning suggestions */
    }
    #ticker {
      padding: 8px;
      font-size: 16px;
      border: 1px solid #444;
      border-radius: 4px 0 0 4px;
      flex-grow: 1;
      border-right: none;
      background-color: #1a1a1a;
      color: #e0e0e0;
    }
    #connectBtn {
      padding: 8px 16px;
      font-size: 16px;
      border: 1px solid #444;
      border-radius: 0 4px 4px 0;
      background-color: orange;
      color: black;
      cursor: pointer;
      white-space: nowrap;
    }
    #connectBtn:hover {
      background-color: darkorange;
    }
    .price-pnl-container {
      display: flex;
      gap: 16px;
      margin-bottom: 10px;
    }
    #currentPriceDisplay, #pnlEl {
      padding: 8px 12px;
      font-size: 16px;
      background-color: #1a1a1a;
      border: 1px solid #333;
      border-radius: 4px;
      text-align: left;
      display: inline-block;
      box-sizing: border-box;
    }
    #currentPriceDisplay {
      min-width: 80px; /* Lebar minimum */
    }
    #pnlEl {
      min-width: 120px;
    }
    /* Styles for crypto suggestions */
    #suggestionsCtn {
      position: absolute;
      top: 100%;
      left: 0;
      width: calc(100% - 94px);
      max-height: 200px;
      overflow-y: auto;
      background-color: #1a1a1a;
      border: 1px solid #444;
      border-radius: 0 0 4px 4px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.3);
      z-index: 10;
      display: none;
    }
    .suggestion-item {
      padding: 8px 10px;
      cursor: pointer;
      color: #e0e0e0;
    }
    .suggestion-item:hover {
      background-color: #2a2a2a;
    }
    /* Styles for position dropdown and input */
    #tradingStatus {
      padding: 8px;
      font-size: 16px;
      border: 1px solid #444;
      border-radius: 4px;
      background-color: #1a1a1a;
      color: #e0e0e0;
      width: 150px;
      cursor: pointer;
    }
    #entryPrice {
      padding: 8px;
      font-size: 16px;
      border: 1px solid #444;
      border-radius: 4px;
      margin-left: 10px;
      width: 100px;
      background-color: #1a1a1a;
      color: #e0e0e0;
    }
    /* Styles for price alert section */
    .price-alert-container {
      margin-top: 20px;
      padding-top: 15px;
      border-top: 1px solid #333;
    }
    .price-alert-container h3 {
      margin-top: 0;
      margin-bottom: 10px;
      font-size: 16px;
    }
    .price-alert-row {
      display: flex;
      align-items: center;
      margin-bottom: 10px;
    }
    #alertPrice {
      padding: 8px;
      font-size: 16px;
      border: 1px solid #444;
      border-radius: 4px;
      width: 100px;
      background-color: #1a1a1a;
      color: #e0e0e0;
    }
    #alertCondition {
      padding: 8px;
      font-size: 16px;
      border: 1px solid #444;
      border-radius: 4px;
      background-color: #1a1a1a;
      color: #e0e0e0;
      width: 120px;
      margin-left: 10px;
      cursor: pointer;
    }
    .sound-toggle-container {
      display: flex;
      align-items: center;
      margin-left: 10px;
    }
    .sound-toggle-label {
      margin-right: 8px;
      font-size: 14px;
    }
    .toggle-switch {
      position: relative;
      display: inline-block;
      width: 40px;
      height: 20px;
    }
    .toggle-switch input {
      opacity: 0;
      width: 0;
      height: 0;
    }
    .toggle-slider {
      position: absolute;
      cursor: pointer;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: #333;
      transition: .4s;
      border-radius: 34px;
    }
    .toggle-slider:before {
      position: absolute;
      content: "";
      height: 14px;
      width: 14px;
      left: 3px;
      bottom: 3px;
      background-color: #e0e0e0;
      transition: .4s;
      border-radius: 50%;
    }
    input:checked + .toggle-slider {
      background-color: #2a7fff;
    }
    input:checked + .toggle-slider:before {
      transform: translateX(20px);
    }
    /* Technical analysis styles */
    .technical-analysis-container {
      margin-top: 20px;
      padding-top: 15px;
      border-top: 1px solid #333;
    }
    .technical-analysis-container h3 {
      margin-top: 0;
      margin-bottom: 10px;
      font-size: 16px;
    }
    #output {
      width: 100%;
      height: 200px;
      padding: 8px;
      font-size: 14px;
      border: 1px solid #444;
      border-radius: 4px;
      background-color: #1a1a1a;
      color: #e0e0e0;
      resize: vertical;
      white-space: pre-wrap;
      font-family: monospace;
      overflow-y: auto;
      margin-bottom: 10px;
    }
    #copyBtn {
      padding: 8px 16px;
      font-size: 14px;
      border: 1px solid #444;
      border-radius: 4px;
      background-color: orange;
      color: black;
      cursor: pointer;
      margin-bottom: 10px;
    }
    #copyBtn:hover {
      background-color: darkorange;
    }
    /* Alerts list and notifications */
    .alert-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 5px;
      margin-bottom: 5px;
      background-color: #1a1a1a;
      border: 1px solid #333;
      border-radius: 4px;
    }
    .alert-item.triggered {
      background-color: #1e3a2a;
    }
    .remove-alert {
      background-color: #c62828;
      color: white;
      border: none;
      border-radius: 3px;
      padding: 2px 6px;
      cursor: pointer;
    }
    #alertNotification {
      position: fixed;
      top: 20px;
      right: 20px;
      padding: 15px;
      background-color: #2e7d32;
      color: white;
      border-radius: 5px;
      display: none;
      z-index: 1000;
    }
    #alertsList {
      margin-top: 10px;
      max-height: 150px;
      overflow-y: auto;
    }
    #tradingStatusDisplay {
      margin-top: 10px;
      padding: 5px 10px;
      background-color: #1a1a1a;
      border-radius: 4px;
      display: none;
    }
    button {
      background-color: orange;
      color: black;
      border: 1px solid #444;
      border-radius: 4px;
      padding: 8px 12px;
      cursor: pointer;
    }
    button:hover {
      background-color: darkorange;
    }
    .position-container {
      margin-top: 20px;
      padding-top: 15px;
      border-top: 1px solid #333;
    }
    .position-container h3 {
      margin-top: 0;
      margin-bottom: 10px;
      font-size: 16px;
    }
  </style>
</head>
<body>
  <div class="container">
    <!-- Asset Price Chart Section -->
    <div class="chart-container">
      <canvas id="priceChartEl"></canvas>
    </div>
    <div class="chart-controls">
      <span>1 Minute Timeframe</span>
    </div>
   
    <div class="input-container">
      <input id="ticker" type="text" placeholder="Enter crypto symbol...">
      <button id="connectBtn" onclick="connect()">Connect</button>
      <div id="suggestionsCtn"></div>
    </div>
    <div class="price-pnl-container">
      <div id="currentPriceDisplay">$0.00</div>
      <div id="pnlEl"></div>
    </div>
   
    <!-- Price Alert Section -->
    <div class="price-alert-container">
      <h3>Price Alert</h3>
      <div class="price-alert-row">
        <input id="alertPrice" type="number" placeholder="Alert price">
        <select id="alertCondition">
          <option value="above">Above</option>
          <option value="below">Below</option>
        </select>
        <div class="sound-toggle-container">
          <span class="sound-toggle-label">Sound</span>
          <label class="toggle-switch">
            <input id="soundToggleEl" type="checkbox" checked>
            <span class="toggle-slider"></span>
          </label>
        </div>
      </div>
      <button onclick="addAlert()">Add Alert</button>
      <button onclick="clearAllAlerts()">Clear All</button>
      <div id="alertsList">No notifications set</div>
    </div>
   
    <div class="position-container">
      <h3>Posisi</h3>
      <select id="tradingStatus">
        <option value="no-position">No position</option>
        <option value="open-long">Open long</option>
        <option value="open-short">Open short</option>
        <option value="cut-position">Cut position</option>
      </select>
      <input id="entryPrice" type="number" placeholder="Open price">
      <div id="tradingStatusDisplay"></div>
    </div>
   
    <!-- Technical Analysis Section -->
    <div class="technical-analysis-container">
      <h3>Technical Analysis</h3>
      <div id="output"></div>
      <button id="copyBtn" onclick="copyOutput()">Copy Analysis</button>
    </div>
  </div>

  <!-- Hidden audio element for alerts -->
  <audio id="alertSound" src="https://assets.mixkit.co/sfx/preview/mixkit-alert-quick-chime-766.mp3"></audio>
 
  <!-- Alert notification div -->
  <div id="alertNotification"></div>

  <!-- Include Chart.js library -->
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <!-- Include candlestick chart adapter -->
  <script src="https://cdn.jsdelivr.net/npm/chartjs-chart-financial"></script>
  <!-- Include date adapter for Chart.js -->
  <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>

  <script>
    // Declare global chart variable
    let chart = null;

    // WebSockets and data arrays
    let ws1m, ws15m, ws1h, ws4h, wsOrderBook;
    let prices1m = [];
    let prices15m = [];
    let prices1h = [];
    let prices4h = [];
    let currentPrice = 0;
    let previousPrice = 0;
    let currentTicker = "";
    let priceAlerts = [];
    let orderBookText = '';

    const MAX_DATA_POINTS_1M = 200; // Match the limit from the example
    const MAX_DATA_POINTS_15M = 100;
    const MAX_DATA_POINTS_1H = 150;
    const MAX_DATA_POINTS_4H = 100;

    // DOM elements cache
    const elements = {
      output: document.getElementById("output"),
      currentPriceDisplay: document.getElementById("currentPriceDisplay"),
      alertsList: document.getElementById("alertsList"),
      alertPrice: document.getElementById("alertPrice"),
      alertCondition: document.getElementById("alertCondition"),
      alertNotification: document.getElementById("alertNotification"),
      alertSound: document.getElementById("alertSound"),
      ticker: document.getElementById("ticker"),
      copyBtn: document.getElementById("copyBtn"),
      tradingStatus: document.getElementById("tradingStatus"),
      entryPrice: document.getElementById("entryPrice"),
      tradingStatusDisplay: document.getElementById("tradingStatusDisplay"),
      pnlEl: document.getElementById("pnlEl")
    };

    // Function to draw chart
    function drawChart(data, symbol) {
      const ctx = document.getElementById('priceChartEl').getContext('2d');
      const ohlc = data.map(d => ({
        x: d.time,
        o: d.open,
        h: d.high,
        l: d.low,
        c: d.close
      }));

      if (chart) chart.destroy();
      chart = new Chart(ctx, {
        type: 'candlestick',
        data: { datasets: [{ label: symbol, data: ohlc }] },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          scales: {
            x: {
              type: 'time',
              time: { unit: 'minute', displayFormats: { minute: 'HH:mm' } },
              title: { display: true, text: 'Time', color: '#e0e0e0' }
            },
            y: {
              title: { display: true, text: 'Price (USDT)', color: '#e0e0e0' },
              ticks: { color: '#e0e0e0' },
              beginAtZero: false
            }
          },
          plugins: {
            legend: { labels: { color: '#e0e0e0' } }
          },
          animation: { duration: 0 } // Disable animation for real-time clarity
        }
      });
    }

    // Update fetchDataAndConnect to use the new format
    async function fetchDataAndConnect(ticker) {
      try {
        const [klines1m, klines15m, klines1h, klines4h] = await Promise.all([
          fetch(`https://api.binance.com/api/v3/klines?symbol=${ticker}&interval=1m&limit=${MAX_DATA_POINTS_1M}`)
            .then(res => {
              if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
              return res.json();
            }),
          fetch(`https://api.binance.com/api/v3/klines?symbol=${ticker}&interval=15m&limit=${MAX_DATA_POINTS_15M}`)
            .then(res => {
              if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
              return res.json();
            }),
          fetch(`https://api.binance.com/api/v3/klines?symbol=${ticker}&interval=1h&limit=${MAX_DATA_POINTS_1H}`)
            .then(res => {
              if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
              return res.json();
            }),
          fetch(`https://api.binance.com/api/v3/klines?symbol=${ticker}&interval=4h&limit=${MAX_DATA_POINTS_4H}`)
            .then(res => {
              if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
              return res.json();
            })
        ]);

        // Process kline data in the new format
        prices1m = klines1m.map(d => ({
          time: parseInt(d[0]), // Open time
          open: parseFloat(d[1]),
          high: parseFloat(d[2]),
          low: parseFloat(d[3]),
          close: parseFloat(d[4]),
          volume: parseFloat(d[5])
        }));

        prices15m = klines15m.map(d => ({
          time: parseInt(d[0]),
          open: parseFloat(d[1]),
          high: parseFloat(d[2]),
          low: parseFloat(d[3]),
          close: parseFloat(d[4]),
          volume: parseFloat(d[5])
        }));

        prices1h = klines1h.map(d => ({
          time: parseInt(d[0]),
          open: parseFloat(d[1]),
          high: parseFloat(d[2]),
          low: parseFloat(d[3]),
          close: parseFloat(d[4]),
          volume: parseFloat(d[5])
        }));

        prices4h = klines4h.map(d => ({
          time: parseInt(d[0]),
          open: parseFloat(d[1]),
          high: parseFloat(d[2]),
          low: parseFloat(d[3]),
          close: parseFloat(d[4]),
          volume: parseFloat(d[5])
        }));

        if (prices1m.length > 0) {
          currentPrice = prices1m[prices1m.length - 1].close;
          previousPrice = currentPrice;
          updateCurrentPriceDisplay(ticker, currentPrice);
          updatePNL();
        }

        // Draw initial chart with 1-minute data
        drawChart(prices1m, ticker);

        // Close existing WebSocket connections
        if (ws1m) ws1m.close();
        if (ws15m) ws15m.close();
        if (ws1h) ws1h.close();
        if (ws4h) ws4h.close();
        if (wsOrderBook) wsOrderBook.close();

        // Setup WebSocket for 1-minute timeframe
        ws1m = setupWebSocket(ticker, '1m', (kline) => {
          console.log('1m WebSocket data:', kline); // Debug log
          currentPrice = parseFloat(kline.c);
          updateCurrentPriceDisplay(ticker, currentPrice);
          updatePNL();

          const newCandle = {
            time: parseInt(kline.T), // Close time
            open: parseFloat(kline.o),
            high: parseFloat(kline.h),
            low: parseFloat(kline.l),
            close: parseFloat(kline.c),
            volume: parseFloat(kline.v)
          };

          if (kline.x) { // If candle is closed
            prices1m.push(newCandle);
            if (prices1m.length > MAX_DATA_POINTS_1M) prices1m.shift();
          } else if (prices1m.length > 0) {
            prices1m[prices1m.length - 1] = newCandle; // Update current candle
          } else {
            prices1m.push(newCandle);
          }

          updateAndDisplayData(ticker);
          drawChart(prices1m, ticker); // Update chart
          checkAlerts();
        });

        // Setup WebSocket for 15m timeframe (for indicators)
        ws15m = setupWebSocket(ticker, '15m', (kline) => {
          const newCandle = {
            time: parseInt(kline.T),
            open: parseFloat(kline.o),
            high: parseFloat(kline.h),
            low: parseFloat(kline.l),
            close: parseFloat(kline.c),
            volume: parseFloat(kline.v)
          };

          if (kline.x) {
            prices15m.push(newCandle);
            if (prices15m.length > MAX_DATA_POINTS_15M) prices15m.shift();
          } else if (prices15m.length > 0) {
            prices15m[prices15m.length - 1] = newCandle;
          } else {
            prices15m.push(newCandle);
          }

          updateAndDisplayData(ticker);
        });

        // Setup WebSocket for 1h timeframe
        ws1h = setupWebSocket(ticker, '1h', (kline) => {
          const newCandle = {
            time: parseInt(kline.T),
            open: parseFloat(kline.o),
            high: parseFloat(kline.h),
            low: parseFloat(kline.l),
            close: parseFloat(kline.c),
            volume: parseFloat(kline.v)
          };

          if (kline.x) {
            prices1h.push(newCandle);
            if (prices1h.length > MAX_DATA_POINTS_1H) prices1h.shift();
          } else if (prices1h.length > 0) {
            prices1h[prices1h.length - 1] = newCandle;
          } else {
            prices1h.push(newCandle);
          }

          updateAndDisplayData(ticker);
        });

        // Setup WebSocket for 4h timeframe
        ws4h = setupWebSocket(ticker, '4h', (kline) => {
          const newCandle = {
            time: parseInt(kline.T),
            open: parseFloat(kline.o),
            high: parseFloat(kline.h),
            low: parseFloat(kline.l),
            close: parseFloat(kline.c),
            volume: parseFloat(kline.v)
          };

          if (kline.x) {
            prices4h.push(newCandle);
            if (prices4h.length > MAX_DATA_POINTS_4H) prices4h.shift();
          } else if (prices4h.length > 0) {
            prices4h[prices4h.length - 1] = newCandle;
          } else {
            prices4h.push(newCandle);
          }

          updateAndDisplayData(ticker);
        });

        // Setup WebSocket for order book data
        const baseTicker = ticker.endsWith('USDT') ? ticker.slice(0, -4) : ticker;
        const maxRows = 5;
        wsOrderBook = new WebSocket(`wss://stream.binance.com:9443/ws/${ticker.toLowerCase()}@depth`);

        wsOrderBook.onmessage = (event) => {
          const data = JSON.parse(event.data);
          const bids = data.b ? data.b.slice(0, maxRows) : [];
          const asks = data.a ? data.a.slice(0, maxRows) : [];

          let liquidityText = '\nBids (Buy Orders):\n';
          bids.forEach(([price, volume]) => {
            liquidityText += `  - ${formatMarketPrice(parseFloat(price))} (${parseFloat(volume)} ${baseTicker})\n`;
          });

          liquidityText += '\nAsks (Sell Orders):\n';
          asks.forEach(([price, volume]) => {
            liquidityText += `  - ${formatMarketPrice(parseFloat(price))} (${parseFloat(volume)} ${baseTicker})\n`;
          });

          orderBookText = liquidityText;
          updateAndDisplayData(ticker);
        };

        currentTicker = ticker;
        updateTradingStatusDisplay();
        updatePNL();

      } catch (error) {
        console.error("Failed to fetch historical data or connect:", error);
        elements.output.innerText = `Error: ${error.message}. Please check ticker symbol or network connection.`;
        elements.currentPriceDisplay.style.display = "none";
      }
    }

    // Function to calculate PNL (Profit and Loss)
    function calculatePNL(currentPrice, entryPrice, positionType) {
      if (!entryPrice || isNaN(entryPrice) || entryPrice <= 0 || !currentPrice) {
        return null; // No valid entry price or current price
      }
     
      entryPrice = parseFloat(entryPrice);
      currentPrice = parseFloat(currentPrice);
     
      if (positionType === "open-long") {
        return ((currentPrice - entryPrice) / entryPrice) * 100;
      } else if (positionType === "open-short") {
        return ((entryPrice - currentPrice) / entryPrice) * 100;
      }
     
      return null; // No position or invalid position type
    }

    // Function to update PNL display
    function updatePNL() {
      const positionType = elements.tradingStatus.value;
      const entryPrice = elements.entryPrice.value;
     
      if ((positionType === "open-long" || positionType === "open-short") && entryPrice) {
        const pnl = calculatePNL(currentPrice, entryPrice, positionType);
       
        if (pnl !== null) {
          const pnlText = pnl.toFixed(2) + "%";
          const pnlColor = pnl >= 0 ? "#4caf50" : "#ff5252";
         
          elements.pnlEl.innerHTML = `<span style="color:${pnlColor};">PNL: ${pnlText}</span>`;
          elements.pnlEl.style.display = "block";
        } else {
          elements.pnlEl.innerHTML = "";
        }
      } else {
        elements.pnlEl.innerHTML = "";
      }
    }

    // Function to format price with appropriate decimal places
    function formatPrice(price) {
      price = Number(price);

      if (price >= 3) {
        return Math.floor(price * 100) / 100;
      } else if (price >= 1) {
        return Math.floor(price * 10000) / 10000;
      } else if (price >= 0.0001) {
        const priceStr = price.toString();
        const decimalIndex = priceStr.indexOf('.');
        if (decimalIndex === -1) {
          return priceStr + '.0000000000';
        }
        const desiredLength = decimalIndex + 1 + 10;
        return priceStr.substring(0, Math.min(priceStr.length, desiredLength));
      } else {
        const priceStr = price.toString();
        const match = priceStr.match(/^0\.0*(\d+)/);
        if (match) {
          const zeroCount = match[0].length - 2 - match[1].length;
          return `0.0{${zeroCount}}${match[1]}`;
        } else {
          return "0.00000000";
        }
      }
    }

    // LocalStorage management functions
    function saveAlertsToLocalStorage() {
      localStorage.priceAlerts = JSON.stringify(priceAlerts);
    }

    function loadAlertsFromLocalStorage() {
      if (localStorage.priceAlerts) {
        priceAlerts = JSON.parse(localStorage.priceAlerts);
        renderAlerts();
      }
    }

    // Market price formatter
    function formatMarketPrice(price) {
      price = Number(price);

      if (price >= 3) {
        return price.toFixed(2);
      } else if (price >= 1) {
        return price.toFixed(4);
      } else if (price >= 0.0001) {
        return price.toFixed(10);
      } else {
        const priceStr = price.toFixed(15).replace(/0+$/, '');
        const match = priceStr.match(/^0\.0*(\d+)/);
        if (match) {
          const zeroCount = match[0].length - 2 - match[1].length;
          const digits = match[1].substring(0, 5);
          return `0.0{${zeroCount}}${digits}`;
        } else {
          return "0.00000000";
        }
      }
    }

    // LocalStorage management functions
    function saveAlertPriceToLocalStorage() {
      localStorage.alertPrice = elements.alertPrice.value;
    }

    function loadAlertPriceFromLocalStorage() {
      if (localStorage.alertPrice) {
        elements.alertPrice.value = localStorage.alertPrice;
      }
    }

    function saveAlertConditionToLocalStorage() {
      localStorage.alertCondition = elements.alertCondition.value;
    }

    function loadAlertConditionFromLocalStorage() {
      if (localStorage.alertCondition) {
        elements.alertCondition.value = localStorage.alertCondition;
      }
    }

    function saveTradingStatusToLocalStorage() {
      localStorage.tradingStatus = elements.tradingStatus.value;
    }

    function loadTradingStatusFromLocalStorage() {
      if (localStorage.tradingStatus) {
        elements.tradingStatus.value = localStorage.tradingStatus;
      }
    }

    function saveEntryPriceToLocalStorage() {
      localStorage.entryPrice = elements.entryPrice.value;
    }

    function loadEntryPriceFromLocalStorage() {
      if (localStorage.entryPrice) {
        elements.entryPrice.value = localStorage.entryPrice;
      }
    }

    // Update trading status display
    function updateTradingStatusDisplay() {
      const status = elements.tradingStatus.value;
      const price = elements.entryPrice.value;
     
      if (status && price) {
        elements.tradingStatusDisplay.textContent = `${status} ${price}`;
        elements.tradingStatusDisplay.style.display = "block";
      } else if (status) {
        elements.tradingStatusDisplay.textContent = status;
        elements.tradingStatusDisplay.style.display = "block";
      } else {
        elements.tradingStatusDisplay.style.display = "none";
      }
    }

    function formatTickerSymbol(input) {
      const ticker = input.trim().toUpperCase();
      return ticker.includes("USDT") ? ticker : ticker + "USDT";
    }

    // Helper function to setup WebSockets
    function setupWebSocket(ticker, interval, onKline) {
      const ws = new WebSocket(`wss://stream.binance.com:9443/ws/${ticker.toLowerCase()}@kline_${interval}`);
     
      ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        if (data.k) {
          onKline(data.k);
        }
      };
     
      ws.onerror = (error) => {
        console.error(`WebSocket ${interval} Error:`, error);
        if (interval === '15m') {
          elements.output.innerText = `WebSocket ${interval} Error. Please check console.`;
        }
      };
     
      ws.onclose = () => {
        console.log(`WebSocket ${interval} closed.`);
      };
     
      return ws;
    }

    // Update current price display with color change
    function updateCurrentPriceDisplay(ticker, price) {
      let priceColor = "#e0e0e0";
     
      if (price < previousPrice) {
        priceColor = "#ff5252";
      } else if (price > previousPrice) {
        priceColor = "#4caf50";
      }
     
      elements.currentPriceDisplay.innerHTML = `<span style="color:${priceColor};">${formatPrice(price)} USDT</span>`;
      elements.currentPriceDisplay.style.display = "block";
     
      previousPrice = price;
    }

    function connect() {
      const userInput = elements.ticker.value;
      if (!userInput) {
        alert("Enter valid ticker!");
        return;
      }
      const ticker = formatTickerSymbol(userInput);
      fetchDataAndConnect(ticker);
    }

    // Update and display data
    function updateAndDisplayData(ticker) {
      const indicators15m = calculateIndicators(prices15m);
      const indicators1h = calculateIndicators(prices1h);
      const indicators4h = calculateIndicators(prices4h);
     
      displayData(
        ticker,
        indicators15m,
        prices15m.length > 0 ? prices15m[prices15m.length - 1].close : 0,
        prices15m.length > 0 ? prices15m[prices15m.length - 1].volume : 0,
        indicators1h,
        prices1h.length > 0 ? prices1h[prices1h.length - 1].close : 0,
        prices1h.length > 0 ? prices1h[prices1h.length - 1].volume : 0,
        indicators4h,
        prices4h.length > 0 ? prices4h[prices4h.length - 1].close : 0,
        prices4h.length > 0 ? prices4h[prices4h.length - 1].volume : 0
      );
    }

    // Calculate EMA
    function calculateEMA(values, period) {
      if (values.length < period) return [];
     
      const k = 2 / (period + 1);
      let emaArray = [];
     
      // Calculate initial SMA
      let sum = 0;
      for (let i = 0; i < period; i++) {
        sum += values[i];
      }
      emaArray.push(sum / period);

      // Calculate subsequent EMAs
      for (let i = period; i < values.length; i++) {
        emaArray.push(values[i] * k + emaArray[emaArray.length - 1] * (1 - k));
      }
     
      return emaArray;
    }

    // Calculate RSI with EMA smoothing
    function calculateRSI(data, period) {
      if (data.length < period + 1) return null;

      let gains = [];
      let losses = [];

      // Calculate initial gains/losses
      for (let i = 1; i < data.length; i++) {
        const diff = data[i].close - data[i - 1].close;
        gains.push(diff > 0 ? diff : 0);
        losses.push(diff < 0 ? Math.abs(diff) : 0);
      }

      // Get data for RSI period
      const periodGains = gains.slice(gains.length - period);
      const periodLosses = losses.slice(losses.length - period);

      // Calculate first average gain/loss
      let avgGain = periodGains.reduce((a, b) => a + b, 0) / period;
      let avgLoss = periodLosses.reduce((a, b) => a + b, 0) / period;

      // Apply EMA smoothing
      for (let i = data.length - period; i < data.length - 1; i++) {
        const currentGain = gains[i];
        const currentLoss = losses[i];
       
        avgGain = (avgGain * (period - 1) + currentGain) / period;
        avgLoss = (avgLoss * (period - 1) + currentLoss) / period;
      }

      if (avgLoss === 0) return 100;
     
      const rs = avgGain / avgLoss;
      return 100 - (100 / (1 + rs));
    }

    // Calculate Stochastic Oscillator
    function calculateStochastic(data, kPeriod = 14, dPeriod = 3) {
      if (data.length < kPeriod) return { stochasticK: null, stochasticD: null };

      const highs = data.map(d => d.high);
      const lows = data.map(d => d.low);
      const closes = data.map(d => d.close);

      let kValues = [];
      for (let i = kPeriod - 1; i < data.length; i++) {
        const periodHighs = highs.slice(i - kPeriod + 1, i + 1);
        const periodLows = lows.slice(i - kPeriod + 1, i + 1);
        const currentClose = closes[i];

        const highestHigh = Math.max(...periodHighs);
        const lowestLow = Math.min(...periodLows);

        // Calculate %K
        const k = (highestHigh - lowestLow) === 0
          ? 50 // Avoid division by zero
          : ((currentClose - lowestLow) / (highestHigh - lowestLow)) * 100;
        kValues.push(k);
      }

      // Calculate %D (3-period SMA of %K)
      let dValues = [];
      if (kValues.length >= dPeriod) {
        for (let i = dPeriod - 1; i < kValues.length; i++) {
          const periodK = kValues.slice(i - dPeriod + 1, i + 1);
          const d = periodK.reduce((a, b) => a + b, 0) / dPeriod;
          dValues.push(d);
        }
      }

      return {
        stochasticK: kValues.length > 0 ? kValues[kValues.length - 1] : null,
        stochasticD: dValues.length > 0 ? dValues[dValues.length - 1] : null
      };
    }

    function calculateIndicators(data) {
      const length = data.length;
      if (length < 26) return {};

      const closingPrices = data.map(d => d.close);

      // RSI calculations
      const rsi14 = calculateRSI(data, 14);
      const rsi6 = calculateRSI(data, 6);

      // EMA calculations
      const ema6Array = calculateEMA(closingPrices, 6);
      const ema12Array = calculateEMA(closingPrices, 12);
      const ema20Array = calculateEMA(closingPrices, 20);
      const ema26Array = calculateEMA(closingPrices, 26);
     
      const ema6 = ema6Array.length > 0 ? ema6Array[ema6Array.length - 1] : null;
      const ema12 = ema12Array.length > 0 ? ema12Array[ema12Array.length - 1] : null;
      const ema20 = ema20Array.length > 0 ? ema20Array[ema20Array.length - 1] : null;
      const ema26 = ema26Array.length > 0 ? ema26Array[ema26Array.length - 1] : null;
     
      const macd = (ema12 && ema26) ? ema12 - ema26 : null;

      // MACD Signal Line
      let macdLineValues = [];
      const minLength = Math.min(ema12Array.length, ema26Array.length);
     
      for(let i = 0; i < minLength; i++) {
        macdLineValues.push(ema12Array[i] - ema26Array[i]);
      }
     
      const signalArray = calculateEMA(macdLineValues, 9);
      const signal = signalArray.length > 0 ? signalArray[signalArray.length - 1] : null;

      // Bollinger Bands
      const bbPeriod = 20;
      if (closingPrices.length < bbPeriod) return {};
     
      const lastBBData = closingPrices.slice(-bbPeriod);
      const sma20 = lastBBData.reduce((a, b) => a + b, 0) / bbPeriod;
      const stdDev = Math.sqrt(lastBBData.reduce((a, b) => a + Math.pow(b - sma20, 2), 0) / bbPeriod);
      const upperBand = sma20 + 2 * stdDev;
      const lowerBand = sma20 - 2 * stdDev;

      // ADX calculation
      const adxPeriod = 14;
      if (data.length < adxPeriod + 1) return {
        rsi14, rsi6, ema6, ema12, ema20, macd, signal, upperBand, lowerBand, sma20, adx: null,
        stochasticK: null, stochasticD: null
      };

      let trs = [];
      let plusDMs = [];
      let minusDMs = [];

      for (let i = 1; i < length; i++) {
        const curr = data[i];
        const prev = data[i - 1];

        // True Range
        const tr = Math.max(
          curr.high - curr.low,
          Math.abs(curr.high - prev.close),
          Math.abs(curr.low - prev.close)
        );
        trs.push(tr);

        // Directional Movement
        const upMove = curr.high - prev.high;
        const downMove = prev.low - curr.low;

        plusDMs.push(upMove > downMove && upMove > 0 ? upMove : 0);
        minusDMs.push(downMove > upMove && downMove > 0 ? downMove : 0);
      }

      // EMA smoothing for indicators
      const smoothedTRs = calculateEMA(trs, adxPeriod);
      const smoothedPlusDMs = calculateEMA(plusDMs, adxPeriod);
      const smoothedMinusDMs = calculateEMA(minusDMs, adxPeriod);

      if (smoothedTRs.length === 0 || smoothedPlusDMs.length === 0 || smoothedMinusDMs.length === 0) {
        return {
          rsi14, rsi6, ema6, ema12, ema20, macd, signal, upperBand, lowerBand, sma20, adx: null,
          stochasticK: null, stochasticD: null
        };
      }

      const currentSmoothedTR = smoothedTRs[smoothedTRs.length - 1];
      const currentSmoothedPlusDM = smoothedPlusDMs[smoothedPlusDMs.length - 1];
      const currentSmoothedMinusDM = smoothedMinusDMs[smoothedMinusDMs.length - 1];

      const plusDI = (currentSmoothedTR === 0) ? 0 : (currentSmoothedPlusDM / currentSmoothedTR) * 100;
      const minusDI = (currentSmoothedTR === 0) ? 0 : (currentSmoothedMinusDM / currentSmoothedTR) * 100;

      // Calculate DX values
      const dxValues = [];
      for (let i = 0; i < smoothedTRs.length; i++) {
        const tempPlusDI = (smoothedTRs[i] === 0) ? 0 : (smoothedPlusDMs[i] / smoothedTRs[i]) * 100;
        const tempMinusDI = (smoothedTRs[i] === 0) ? 0 : (smoothedMinusDMs[i] / smoothedTRs[i]) * 100;
        const dx = (tempPlusDI + tempMinusDI === 0) ? 0 :
                  Math.abs(tempPlusDI - tempMinusDI) / (tempPlusDI + tempMinusDI) * 100;
        dxValues.push(dx);
      }
     
      const adxArray = calculateEMA(dxValues, adxPeriod);
      const adx = adxArray.length > 0 ? adxArray[adxArray.length - 1] : null;

      // Calculate Stochastic Oscillator
      const { stochasticK, stochasticD } = calculateStochastic(data);

      return {
        rsi14, rsi6, ema6, ema12, ema20, macd, signal, upperBand, lowerBand, sma20, adx,
        stochasticK, stochasticD
      };
    }

    function displayData(ticker, ind15m, price15m, volume15m, ind1h, price1h, volume1h, ind4h, price4h, volume4h) {
      // Extract base ticker (remove USDT if it's at the end)
      const baseTicker = ticker.endsWith('USDT') ? ticker.slice(0, -4) : ticker;
     
      // Check for RSI6 error
      const hasRsi6Error =
        (ind15m.rsi6 === 0) ||
        (ind1h.rsi6 === 0) ||
        (ind4h.rsi6 === 0);
     
      const errorMessage = hasRsi6Error ? "FYI RSI6 error\n\n" : "";
     
      // Get trading status and entry price
      const tradingStatus = elements.tradingStatus.value;
      const entryPrice = elements.entryPrice.value;
     
      // Build trading status text
      let tradingStatusText = "";
      if (tradingStatus && entryPrice) {
        tradingStatusText = `Trading Status: leverage 30x, ${tradingStatus} ${entryPrice}\n\n`;
      } else if (tradingStatus) {
        tradingStatusText = `Trading Status: ${tradingStatus} buat 1 opsi, no entry agresif, no spekulatif dan no scalping.\nBuat jawaban singkat saja max 500 karakter.\n\n`;
      }
     
      const text = `${errorMessage}${tradingStatusText}Ticker: ${ticker}${orderBookText}
---
15-Menit Time Frame:
Harga saat ini: ${formatPrice(price15m)}
Volume: ${formatPrice(volume15m)} ${baseTicker}
RSI (14): ${ind15m.rsi14 !== null ? ind15m.rsi14.toFixed(2) : 'N/A'}
RSI (6): ${ind15m.rsi6 !== null ? ind15m.rsi6.toFixed(2) : 'N/A'}
EMA (6): ${ind15m.ema6 ? formatMarketPrice(ind15m.ema6) : 'N/A'}
EMA (12): ${ind15m.ema12 ? formatMarketPrice(ind15m.ema12) : 'N/A'}
EMA (20): ${ind15m.ema20 ? formatMarketPrice(ind15m.ema20) : 'N/A'}
MACD: ${ind15m.macd ? ind15m.macd.toFixed(4) : 'N/A'}
Signal Line: ${ind15m.signal ? ind15m.signal.toFixed(4) : 'N/A'}
Bollinger Bands:
  - Upper: ${ind15m.upperBand ? formatMarketPrice(ind15m.upperBand) : 'N/A'}
  - SMA (20): ${ind15m.sma20 ? formatMarketPrice(ind15m.sma20) : 'N/A'}
  - Lower: ${ind15m.lowerBand ? formatMarketPrice(ind15m.lowerBand) : 'N/A'}
ADX (14): ${ind15m.adx !== null ? ind15m.adx.toFixed(2) : 'N/A'}
Stochastic Oscillator:
  - %K: ${ind15m.stochasticK !== null ? ind15m.stochasticK.toFixed(2) : 'N/A'}
  - %D: ${ind15m.stochasticD !== null ? ind15m.stochasticD.toFixed(2) : 'N/A'}

---
1-Jam Time Frame:
Harga saat ini: ${formatPrice(price1h)}
Volume: ${formatPrice(volume1h)} ${baseTicker}
RSI (14): ${ind1h.rsi14 !== null ? ind1h.rsi14.toFixed(2) : 'N/A'}
RSI (6): ${ind1h.rsi6 !== null ? ind1h.rsi6.toFixed(2) : 'N/A'}
EMA (6): ${ind1h.ema6 ? formatMarketPrice(ind1h.ema6) : 'N/A'}
EMA (12): ${ind1h.ema12 ? formatMarketPrice(ind1h.ema12) : 'N/A'}
EMA (20): ${ind1h.ema20 ? formatMarketPrice(ind1h.ema20) : 'N/A'}
MACD: ${ind1h.macd ? ind1h.macd.toFixed(4) : 'N/A'}
Signal Line: ${ind1h.signal ? ind1h.signal.toFixed(4) : 'N/A'}
Bollinger Bands:
  - Upper: ${ind1h.upperBand ? formatMarketPrice(ind1h.upperBand) : 'N/A'}
  - SMA (20): ${ind1h.sma20 ? formatMarketPrice(ind1h.sma20) : 'N/A'}
  - Lower: ${ind1h.lowerBand ? formatMarketPrice(ind1h.lowerBand) : 'N/A'}
ADX (14): ${ind1h.adx !== null ? ind1h.adx.toFixed(2) : 'N/A'}
Stochastic Oscillator:
  - %K: ${ind1h.stochasticK !== null ? ind1h.stochasticK.toFixed(2) : 'N/A'}
  - %D: ${ind1h.stochasticD !== null ? ind1h.stochasticD.toFixed(2) : 'N/A'}

---
4-Jam Time Frame:
Harga saat ini: ${formatPrice(price4h)}
Volume: ${formatPrice(volume4h)} ${baseTicker}
RSI (14): ${ind4h.rsi14 !== null ? ind4h.rsi14.toFixed(2) : 'N/A'}
RSI (6): ${ind4h.rsi6 !== null ? ind4h.rsi6.toFixed(2) : 'N/A'}
EMA (6): ${ind4h.ema6 ? formatMarketPrice(ind4h.ema6) : 'N/A'}
EMA (12): ${ind4h.ema12 ? formatMarketPrice(ind4h.ema12) : 'N/A'}
EMA (20): ${ind4h.ema20 ? formatMarketPrice(ind4h.ema20) : 'N/A'}
MACD: ${ind4h.macd ? ind4h.macd.toFixed(4) : 'N/A'}
Signal Line: ${ind4h.signal ? ind4h.signal.toFixed(4) : 'N/A'}
Bollinger Bands:
  - Upper: ${ind4h.upperBand ? formatMarketPrice(ind4h.upperBand) : 'N/A'}
  - SMA (20): ${ind4h.sma20 ? formatMarketPrice(ind4h.sma20) : 'N/A'}
  - Lower: ${ind4h.lowerBand ? formatMarketPrice(ind4h.lowerBand) : 'N/A'}
ADX (14): ${ind4h.adx !== null ? ind4h.adx.toFixed(2) : 'N/A'}
Stochastic Oscillator:
  - %K: ${ind4h.stochasticK !== null ? ind4h.stochasticK.toFixed(2) : 'N/A'}
  - %D: ${ind4h.stochasticD !== null ? ind4h.stochasticD.toFixed(2) : 'N/A'}
`;
      elements.output.textContent = text;
    }

    function copyOutput() {
      const outputText = elements.output.textContent;
      navigator.clipboard.writeText(outputText)
        .then(() => {
            elements.copyBtn.textContent = "Copied!";
            setTimeout(() => {
                elements.copyBtn.textContent = "Copy Analysis";
            }, 2000);
        })
        .catch(err => {
            console.error('Failed to copy:', err);
            alert('Failed to copy text to clipboard');
          });
    }

    // Price alert functions
    function addAlert() {
      const price = parseFloat(elements.alertPrice.value);
      const condition = elements.alertCondition.value;
     
      if (isNaN(price) || price <= 0) {
        alert("Please enter a valid price");
        return;
      }
     
      // Generate unique ID
      const alertId = Date.now().toString();
     
      // Create alert object
      const alertObj = {
        id: alertId,
        price: price,
        condition: condition,
        ticker: currentTicker,
        triggered: false
      };
     
      // Add to alerts array
      priceAlerts.push(alertObj);
     
      // Save to localStorage
      saveAlertsToLocalStorage();
     
      // Update alerts display
      renderAlerts();
    }

    function removeAlert(alertId) {
      priceAlerts = priceAlerts.filter(alert => alert.id !== alertId);
      saveAlertsToLocalStorage();
      renderAlerts();
    }

    function clearAllAlerts() {
      priceAlerts = [];
      elements.alertPrice.value = '';
      localStorage.removeItem('alertPrice');
      saveAlertsToLocalStorage();
      renderAlerts();
    }

    function renderAlerts() {
      if (priceAlerts.length === 0) {
        elements.alertsList.innerHTML = "No notifications set";
        return;
      }
     
      let html = "";
      priceAlerts.forEach(alert => {
        const condition = alert.condition === "above" ? ">" : "<";
        const className = alert.triggered ? "alert-item triggered" : "alert-item";
       
        html += `
          <div class="${className}" data-alert-id="${alert.id}">
            <span>${alert.ticker}: Price ${condition} ${formatPrice(alert.price)}</span>
            <button class="remove-alert" onclick="removeAlert('${alert.id}')">X</button>
          </div>
        `;
      });
     
      elements.alertsList.innerHTML = html;
    }

    function checkAlerts() {
      let alertTriggered = false;
      let triggeredMessage = "";
     
      priceAlerts.forEach(alert => {
        let isTriggered = false;
       
        if (alert.condition === "above" && currentPrice > alert.price && !alert.triggered) {
          isTriggered = true;
          triggeredMessage = `${currentTicker} price is now above ${formatPrice(alert.price)}`;
        } else if (alert.condition === "below" && currentPrice < alert.price && !alert.triggered) {
          isTriggered = true;
          triggeredMessage = `${currentTicker} price is now below ${formatPrice(alert.price)}`;
        }
       
        if (isTriggered) {
          alert.triggered = true;
          alertTriggered = true;
         
          // Save updated alert status
          saveAlertsToLocalStorage();
         
          // Update display
          renderAlerts();
         
          // Show notification
          showNotification(triggeredMessage);
        }
      });
     
      return alertTriggered;
    }
     
    function showNotification(message) {
      elements.alertNotification.innerText = message;
      elements.alertNotification.style.display = "block";
     
      // Try to play sound
      try {
        elements.alertSound.play().catch(e => console.log("Audio playback error:", e));
      } catch (e) {
        console.log("Sound notification not supported or audio file error", e);
      }
     
      // Hide after 5 seconds
      setTimeout(() => {
        elements.alertNotification.style.display = "none";
      }, 5000);
    }

    // Initialize localStorage when document is ready
    window.addEventListener('DOMContentLoaded', () => {
      loadAlertsFromLocalStorage();
      loadAlertPriceFromLocalStorage();
      loadAlertConditionFromLocalStorage();
      loadTradingStatusFromLocalStorage();
      loadEntryPriceFromLocalStorage();
     
      // Add event listeners
      elements.alertPrice.addEventListener('input', saveAlertPriceToLocalStorage);
      elements.alertCondition.addEventListener('change', saveAlertConditionToLocalStorage);
      elements.tradingStatus.addEventListener('change', () => {
        saveTradingStatusToLocalStorage();
        updateTradingStatusDisplay();
        updatePNL(); // Update PNL when trading status changes
        if (currentTicker) updateAndDisplayData(currentTicker);
      });
      elements.entryPrice.addEventListener('input', () => {
        saveEntryPriceToLocalStorage();
        updateTradingStatusDisplay();
        updatePNL(); // Update PNL when entry price changes
        if (currentTicker) updateAndDisplayData(currentTicker);
      });
    });
  </script>
</body>
</html>

No comments:

Post a Comment