← Back
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Options Screener Dashboard</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script>
    tailwind.config = {
      darkMode: 'class',
      theme: {
        extend: {
          colors: {
            background: '#0B0E14',
            surface: '#1A1E29',
            surfaceHover: '#232836',
            border: '#2C3245',
            primary: '#3B82F6',
            success: '#10B981',
            danger: '#EF4444',
            warning: '#F59E0B',
            textMain: '#F8FAFC',
            textMuted: '#94A3B8'
          }
        }
      }
    }
  </script>
  <style>
    body {
      background-color: theme('colors.background');
      color: theme('colors.textMain');
      font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    }
    .glass-card {
      background: theme('colors.surface');
      border: 1px solid theme('colors.border');
    }
    .glass-card:hover {
      border-color: theme('colors.primary');
    }
  </style>
</head>
<body class="min-h-screen text-textMain antialiased overflow-x-hidden">

  <!-- Header -->
  <header class="border-b border-border bg-background sticky top-0 z-50">
    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex flex-col sm:flex-row justify-between items-center gap-4">
      <div class="flex items-center gap-3">
        <div class="w-8 h-8 rounded bg-primary/20 flex items-center justify-center text-primary font-bold shadow-[0_0_15px_rgba(59,130,246,0.5)]">
          &Omega;
        </div>
        <h1 class="text-xl font-bold tracking-tight">Options Screener</h1>
      </div>
      
      <div id="header-stats" class="flex gap-6 text-sm">
        <div class="flex items-center gap-2">
          <span class="text-textMuted">BTC:</span>
          <span id="btc-price" class="font-mono font-semibold text-textMain animate-pulse">Loading...</span>
        </div>
        <div class="flex items-center gap-2">
          <span class="text-textMuted">ETH:</span>
          <span id="eth-price" class="font-mono font-semibold text-textMain animate-pulse">Loading...</span>
        </div>
      </div>
      
      <div class="text-xs text-textMuted flex items-center gap-2">
        <span class="relative flex h-2 w-2">
          <span id="live-ping" class="animate-ping absolute inline-flex h-full w-full rounded-full bg-success opacity-75 hidden"></span>
          <span id="live-dot" class="relative inline-flex rounded-full h-2 w-2 bg-textMuted"></span>
        </span>
        <span id="last-update">Connecting...</span>
      </div>
    </div>
  </header>

  <!-- Main Content -->
  <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
    <div class="mb-6 flex justify-between items-end">
      <div>
        <h2 class="text-2xl font-bold mb-1">Trading Signals</h2>
        <p class="text-textMuted text-sm">Real-time analysis from Unusual Volume, Skew, Gamma, and Strategies</p>
      </div>
      <div class="text-sm font-medium text-textMuted">
        <span id="signal-count" class="text-white font-bold">0</span> signals found
      </div>
    </div>
    
    <div id="error-container" class="hidden bg-danger/10 border border-danger/30 text-danger px-4 py-3 rounded-lg mb-6">
      <p id="error-message"></p>
    </div>

    <!-- Signals Grid -->
    <div id="signals-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
      <p class="text-textMuted animate-pulse col-span-full">Loading live dashboard data...</p>
    </div>
  </main>

  <script>
    const CONFIG = { API_URL: '/api/dashboard' };

    const formatCurrency = (val) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(val);
    const formatDate = (isoStr) => new Date(isoStr).toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute:'2-digit', second:'2-digit' });

    const getColors = (signal) => {
      let bg = 'bg-surfaceHover', text = 'text-textMuted';
      if(signal.direction.includes('BULL')) { text = 'text-success'; bg = 'bg-success/10 border-success/20'; }
      else if(signal.direction.includes('BEAR')) { text = 'text-danger'; bg = 'bg-danger/10 border-danger/20'; }
      
      let sevColor = 'text-textMuted bg-surfaceHover';
      if(signal.severity === 'EXTREME') sevColor = 'text-white bg-danger shadow-[0_0_8px_rgba(239,68,68,0.6)]';
      if(signal.severity === 'HIGH') sevColor = 'text-danger bg-danger/20';
      if(signal.severity === 'MEDIUM') sevColor = 'text-warning bg-warning/20';
      
      let confBg = 'bg-textMuted';
      if (signal.confidence >= 75) confBg = 'bg-success';
      else if (signal.confidence >= 55) confBg = 'bg-warning';

      return { dirClass: `${text} ${bg} border`, sevClass: sevColor, confBg };
    };

    const renderCard = (s) => {
      const c = getColors(s);
      return `
        <div class="glass-card rounded-xl p-5 flex flex-col transition-all duration-300 relative group">
          <div class="flex justify-between items-start mb-3">
            <div class="flex items-center gap-2">
              <span class="font-bold text-lg text-white">${s.underlying}</span>
              <span class="text-[10px] px-2 py-0.5 rounded-full uppercase tracking-wider font-semibold ${c.dirClass}">${s.direction.replace(/_/g,' ')}</span>
            </div>
            ${s.severity ? `<span class="text-[10px] uppercase font-bold px-2 py-1 rounded tracking-wider ${c.sevClass}">${s.severity}</span>` : ''}
          </div>
          
          <h3 class="font-semibold text-blue-400 text-sm mb-1">${s.strategy}</h3>
          <p class="text-white text-sm leading-snug mb-3 font-medium">${s.description || 'No description provided'}</p>
          <p class="text-[12px] text-textMuted flex-grow mb-5 leading-relaxed">${s.rationale}</p>
          
          <div class="mt-auto">
            <div class="flex justify-between text-xs mb-1">
              <span class="text-textMuted font-medium">Confidence Match</span>
              <span class="text-white font-bold">${s.confidence}%</span>
            </div>
            <div class="w-full bg-surfaceHover/80 rounded-full h-1.5 overflow-hidden">
              <div class="h-1.5 rounded-full ${c.confBg}" style="width: ${Math.max(5, s.confidence)}%"></div>
            </div>
          </div>
        </div>
      `;
    };

    const fetchAPI = async () => {
      try {
        const res = await fetch(CONFIG.API_URL);
        const data = await res.json();
        if(!data.success) throw new Error(data.error || 'Server returned false');
        
        document.getElementById('btc-price').textContent = formatCurrency(data.spotPrices.BTC);
        document.getElementById('eth-price').textContent = formatCurrency(data.spotPrices.ETH);
        
        const bPriceClasses = document.getElementById('btc-price').classList;
        bPriceClasses.remove('animate-pulse');
        document.getElementById('eth-price').classList.remove('animate-pulse');

        document.getElementById('last-update').textContent = 'Live \u2022 Local';
        document.getElementById('live-dot').classList.replace('bg-textMuted', 'bg-success');
        document.getElementById('live-ping').classList.remove('hidden');

        document.getElementById('signal-count').textContent = data.signals ? data.signals.length : 0;
        
        const grid = document.getElementById('signals-grid');
        grid.innerHTML = data.signals && data.signals.length 
          ? data.signals.map(renderCard).join('') 
          : '<div class="col-span-full py-10 text-center text-textMuted border border-border border-dashed rounded-xl">No active signals found.</div>';
          
        document.getElementById('error-container').classList.add('hidden');
      } catch (err) {
        document.getElementById('error-message').textContent = err.message;
        document.getElementById('error-container').classList.remove('hidden');
        document.getElementById('live-dot').classList.replace('bg-success', 'bg-danger');
      }
    };

    fetchAPI();
    setInterval(fetchAPI, 10000);
  </script>
</body>
</html>

📜 Git History

163bb5dfeat: migrate to options-screener-v2 folder to isolate deployment4 months ago
Show last diff
Loading...