← Back
/**
 * Settings Panel — Slide-out panel with sections
 * Sections: Charts, Densities, Signals, Layout, Reset
 * Persistence: localStorage (free) + server sync (pro, future)
 *
 * Usage: settingsPanel.get('candleType'), settingsPanel.onChange(cb)
 */
const settingsPanel = (() => {
  // --- Defaults ---
  const DEFAULTS = {
    // Charts
    candleType: 'Candlestick', // Candlestick, Bar, Line, Area
    logScale: false,
    volumeHeight: 15,          // % of chart
    barWidth: 2,               // thin=1, normal=2, wide=3
    showGrid: true,
    showWatermark: true,
    defaultTF: '5m',           // default timeframe

    // Sidebar & Grid
    cardsPerRow: 4,            // 3, 4, 5, 6
    cardSize: 'normal',        // compact, normal, large
    colChg: true,              // show Chg% column
    colNatr: true,             // show NATR column
    colVol: true,              // show Vol column

    // Watchlist
    watchlistOnly: false,      // show only watchlist coins in sidebar

    // Theme
    theme: 'dark',             // dark, darker, amoled
    candleUp: '#22c55e',       // green
    candleDown: '#ef4444',     // red

    // Densities
    densityEnabled: true,
    densitySeverityLarge: 5.0,    // multiplier for Large
    densitySeverityMedium: 3.5,   // multiplier for Medium
    densitySeveritySmall: 2.0,    // multiplier for Small
    densityDepthPct: 5.0,         // depth % from price
    densityTTLMin: 1,             // min lifetime in minutes

    // Density blacklist
    densityBlacklist: 'USDC,FDUSD,TUSD,USDP,DAI,USDD,EUR',

    // Signals
    signalMinRatio: 2,            // volume spike min ratio (2x-20x)
    signalMinConfidence: 50,      // min confidence to show signal (30-90)
    signalOfMinConf: 50,          // separate min confidence for order-flow signals (50-95)
    signalNotifications: false,   // in-tab browser notifications (toast + Notification API)
    signalPush: false,            // server push notifications (works with browser closed / on phone)
    signalSound: false,           // sound on new signal
    signalCooldown: 5,            // minutes between same-symbol alerts (1, 5, 15, 30)
    signalWatchlistOnly: false,   // only show signals for watchlist coins
    signalTypes: ['volume_spike', 'oi_longs', 'oi_shorts', 'oi_squeeze', 'oi_liquidation', 'oi_divergence', 'oi_funding_squeeze', 'liq_sweep', 'channel'], // enabled signal types for push

    // Channel Signal
    channelTf5m: true,            // show/push 5m channel signals
    channelTf15m: true,           // show/push 15m channel signals
    channelTf1h: true,            // show/push 1h channel signals

    // Liq Sweep
    sweepLevelSwing: true,        // show signals from swing high/low levels
    sweepLevelWall: true,         // show signals from order book walls
    sweepLevelRound: true,        // show signals from round numbers
    sweepMinWickPct: 60,          // min wick ratio % to show (60-90)

    // Signals on charts
    showSignalsOnCharts: false,   // show signal markers on mini-charts and modal

    // Indicators
    indicatorOI: false,           // show OI overlay on charts
    indicatorOIColor: '#eab308',  // OI line color (yellow)

    // Data
    autoRefresh: true,            // auto-refresh data
    refreshInterval: 30,          // seconds (10, 30, 60, 120)
    defaultSort: 'change',        // change, volume, natr, symbol
    defaultSortDir: 'asc',        // asc, desc
    minVolume: 50,                // min 24h volume in $M (0=off, 10, 50, 100, 250, 500)

    // Layout
    layout: '1',              // '1', '2h', '2v', '4', '1+3'

    // Channel overlays
    ch_keltner: false,
    ch_regression: false,

    // Drawing defaults
    drawLineStyle: 'solid',       // solid, dashed, dotted
    drawDefaultColor: '#ffffff',
  }

  const STORAGE_KEY = 'fs_settings'
  const WL_KEY = 'fs_watchlist'
  let settings = {}
  let listeners = []
  let panelOpen = false
  let watchlist = new Set()

  // --- Watchlist ---
  function loadWatchlist() {
    try { watchlist = new Set(JSON.parse(localStorage.getItem(WL_KEY) || '[]')) } catch { watchlist = new Set() }
  }
  function saveWatchlist() { lsSet(WL_KEY, JSON.stringify([...watchlist])) }
  function wlAdd(sym) { watchlist.add(sym); saveWatchlist(); notify('__watchlist', [...watchlist]) }
  function wlRemove(sym) { watchlist.delete(sym); saveWatchlist(); notify('__watchlist', [...watchlist]) }
  function wlToggle(sym) { watchlist.has(sym) ? wlRemove(sym) : wlAdd(sym) }
  function wlHas(sym) { return watchlist.has(sym) }
  function wlList() { return [...watchlist] }
  function wlClear() { watchlist.clear(); saveWatchlist(); notify('__watchlist', []) }

  // Sync watchlist across tabs via storage event
  window.addEventListener('storage', (e) => {
    if (e.key === WL_KEY) {
      try { watchlist = new Set(JSON.parse(e.newValue || '[]')) } catch { return }
      notify('__watchlist', [...watchlist])
    }
    if (e.key === STORAGE_KEY) {
      try { settings = { ...DEFAULTS, ...JSON.parse(e.newValue || '{}') } } catch { return }
      notify('__settings', settings)
    }
  })

  // --- Load / Save ---
  function load() {
    try {
      const raw = localStorage.getItem(STORAGE_KEY)
      settings = raw ? { ...DEFAULTS, ...JSON.parse(raw) } : { ...DEFAULTS }
      // Migration: fix old density defaults that filtered everything
      if (settings.densityTTLMin === 15) settings.densityTTLMin = DEFAULTS.densityTTLMin
      if (settings.densityDepthPct === 3.0) settings.densityDepthPct = DEFAULTS.densityDepthPct
      if (!settings.densityBlacklist) settings.densityBlacklist = DEFAULTS.densityBlacklist
      save()
    } catch {
      settings = { ...DEFAULTS }
    }
  }

  function save() {
    lsSet(STORAGE_KEY, JSON.stringify(settings))
    // Future: if pro, sync to server via authUI.authFetch
  }

  function get(key) {
    return settings[key] !== undefined ? settings[key] : DEFAULTS[key]
  }

  function set(key, value) {
    settings[key] = value
    save()
    notify(key, value)
  }

  function getAll() { return { ...settings } }

  function resetAll() {
    settings = { ...DEFAULTS }
    save()
    listeners.forEach(cb => cb('__reset', null))
    renderActiveSection()
  }

  // --- Change listeners ---
  function onChange(cb) { listeners.push(cb) }
  function notify(key, value) { listeners.forEach(cb => cb(key, value)) }

  // --- Panel DOM ---
  const SECTIONS = [
    { id: 'charts',    icon: '📊', label: 'Charts' },
    { id: 'densities', icon: '◉', label: 'Densities' },
    { id: 'signals',   icon: '🔔', label: 'Signals' },
    { id: 'watchlist', icon: '⭐', label: 'Watchlist' },
    { id: 'theme',     icon: '🎨', label: 'Theme' },
    { id: 'data',      icon: '📡', label: 'Data' },
    { id: 'indicators',icon: '📉', label: 'Indicators' },
    { id: 'layout',    icon: '⊞', label: 'Layout' },
    { id: 'reset',     icon: '🗑', label: 'Reset' },
  ]
  let activeSection = 'charts'

  function createPanel() {
    // Overlay
    const overlay = document.createElement('div')
    overlay.id = 'settingsOverlay'
    overlay.className = 'settings-overlay hidden'
    overlay.addEventListener('click', closePanel)

    // Panel
    const panel = document.createElement('div')
    panel.id = 'settingsPanel'
    panel.className = 'settings-panel hidden'

    panel.innerHTML = `
      <div class="sp-header">
        <button class="sp-back hidden" id="spBack">←</button>
        <h3 class="sp-title">Settings</h3>
        <button class="sp-close" id="spClose">&times;</button>
      </div>
      <div class="sp-body">
        <div class="sp-nav" id="spNav">
          ${SECTIONS.map(s => `
            <button class="sp-nav-item${s.id === activeSection ? ' active' : ''}" data-section="${s.id}">
              <span class="sp-nav-icon">${s.icon}</span>
              <span class="sp-nav-label">${s.label}</span>
            </button>
          `).join('')}
        </div>
        <div class="sp-content" id="spContent"></div>
      </div>
    `

    document.body.appendChild(overlay)
    document.body.appendChild(panel)

    // Events
    panel.querySelector('#spClose').addEventListener('click', closePanel)
    panel.querySelector('#spBack').addEventListener('click', () => {
      panel.querySelector('#spBack').classList.add('hidden')
      panel.querySelector('.sp-title').textContent = 'Settings'
      panel.querySelector('#spNav').classList.remove('hidden')
      panel.querySelector('#spContent').innerHTML = ''
    })

    panel.querySelectorAll('.sp-nav-item').forEach(btn => {
      btn.addEventListener('click', () => {
        activeSection = btn.dataset.section
        panel.querySelectorAll('.sp-nav-item').forEach(b => b.classList.remove('active'))
        btn.classList.add('active')
        renderActiveSection()

        // Mobile: hide nav, show back
        if (window.innerWidth < 600) {
          panel.querySelector('#spNav').classList.add('hidden')
          panel.querySelector('#spBack').classList.remove('hidden')
          panel.querySelector('.sp-title').textContent = SECTIONS.find(s => s.id === activeSection)?.label || 'Settings'
        }
      })
    })

    renderActiveSection()
  }

  function renderActiveSection() {
    const content = document.getElementById('spContent')
    if (!content) return

    switch (activeSection) {
      case 'charts': content.innerHTML = renderChartsSection(); break
      case 'densities': content.innerHTML = renderDensitiesSection(); break
      case 'signals': content.innerHTML = renderSignalsSection(); break
      case 'watchlist': content.innerHTML = renderWatchlistSection(); break
      case 'theme': content.innerHTML = renderThemeSection(); break
      case 'data': content.innerHTML = renderDataSection(); break
      case 'indicators': content.innerHTML = renderIndicatorsSection(); break
      case 'layout': content.innerHTML = renderLayoutSection(); break
      case 'reset': content.innerHTML = renderResetSection(); break
    }

    bindSectionEvents(content)
  }

  // --- Section Renderers ---

  function updateCandlePreview(container, up, down) {
    container.querySelectorAll('.sp-candle.up').forEach(c => c.style.setProperty('--c', up))
    container.querySelectorAll('.sp-candle.down').forEach(c => c.style.setProperty('--c', down))
  }

  const CANDLE_PRESETS = [
    { label: 'Classic', up: '#22c55e', down: '#ef4444' },
    { label: 'Blue/Orange', up: '#3b82f6', down: '#f97316' },
    { label: 'Cyan/Pink', up: '#06b6d4', down: '#ec4899' },
    { label: 'Lime/Purple', up: '#84cc16', down: '#a855f7' },
    { label: 'TradingView', up: '#26a69a', down: '#ef5350' },
    { label: 'Monochrome', up: '#94a3b8', down: '#475569' },
  ]

  function renderThemeSection() {
    const currentUp = get('candleUp')
    const currentDown = get('candleDown')
    return `
      <div class="sp-section">
        <div class="sp-section-title">Theme</div>
        <div class="sp-radio-group">
          ${['dark', 'darker', 'amoled'].map(t => `
            <label class="sp-radio">
              <input type="radio" name="theme" value="${t}" ${get('theme') === t ? 'checked' : ''} data-key="theme" />
              <span>${t.charAt(0).toUpperCase() + t.slice(1)}</span>
            </label>
          `).join('')}
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Candle Colors</div>
        <div class="sp-color-presets">
          ${CANDLE_PRESETS.map(p => `
            <button class="sp-color-preset${currentUp === p.up && currentDown === p.down ? ' active' : ''}"
                    data-up="${p.up}" data-down="${p.down}" title="${p.label}">
              <span class="sp-color-dot" style="background:${p.up}"></span>
              <span class="sp-color-dot" style="background:${p.down}"></span>
              <span class="sp-color-name">${p.label}</span>
            </button>
          `).join('')}
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Custom Colors</div>
        <div class="sp-color-row">
          <label class="sp-color-label">
            <span>Up</span>
            <input type="color" value="${currentUp}" data-key="candleUp" class="sp-color-input" />
          </label>
          <label class="sp-color-label">
            <span>Down</span>
            <input type="color" value="${currentDown}" data-key="candleDown" class="sp-color-input" />
          </label>
        </div>
        <div class="sp-candle-preview">
          <div class="sp-candle up" style="--c:${currentUp}"></div>
          <div class="sp-candle down" style="--c:${currentDown}"></div>
          <div class="sp-candle up small" style="--c:${currentUp}"></div>
          <div class="sp-candle down" style="--c:${currentDown}"></div>
          <div class="sp-candle up" style="--c:${currentUp}"></div>
          <div class="sp-candle down small" style="--c:${currentDown}"></div>
        </div>
      </div>
    `
  }

  function renderWatchlistSection() {
    const list = wlList()
    return `
      <div class="sp-section">
        <div class="sp-section-title">Your Watchlist (${list.length})</div>
        ${list.length === 0 ? `<p class="sp-hint">Click ⭐ on any coin in the sidebar to add it</p>` : `
          <div class="sp-wl-list">
            ${list.map(sym => `
              <div class="sp-wl-item">
                <span class="sp-wl-name">${sym.replace('USDT', '')}</span>
                <button class="sp-wl-remove" data-sym="${sym}" title="Remove">✕</button>
              </div>
            `).join('')}
          </div>
        `}
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Filter</div>
        <label class="sp-toggle">
          <input type="checkbox" id="spWlOnly" ${get('watchlistOnly') ? 'checked' : ''} />
          <span>Show watchlist only in sidebar</span>
        </label>
      </div>
      ${list.length > 0 ? `
        <div class="sp-section">
          <button class="sp-danger-btn" id="spClearWatchlist">Clear watchlist</button>
        </div>
      ` : ''}
    `
  }

  function renderDataSection() {
    return `
      <div class="sp-section">
        <div class="sp-section-title">Auto-Refresh</div>
        <label class="sp-toggle">
          <input type="checkbox" ${get('autoRefresh') ? 'checked' : ''} data-key="autoRefresh" />
          <span>Enable auto-refresh</span>
        </label>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Refresh Interval</div>
        <div class="sp-radio-group">
          ${[10, 30, 60, 120].map(s => `
            <label class="sp-radio">
              <input type="radio" name="refreshInterval" value="${s}" ${get('refreshInterval') === s ? 'checked' : ''} data-key="refreshInterval" />
              <span>${s < 60 ? s + 's' : (s / 60) + 'min'}</span>
            </label>
          `).join('')}
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Default Sort</div>
        <div class="sp-radio-group">
          ${[
            { v: 'change', l: 'Chg%' },
            { v: 'volume', l: 'Volume' },
            { v: 'natr', l: 'NATR' },
            { v: 'symbol', l: 'Name' },
          ].map(s => `
            <label class="sp-radio">
              <input type="radio" name="defaultSort" value="${s.v}" ${get('defaultSort') === s.v ? 'checked' : ''} data-key="defaultSort" />
              <span>${s.l}</span>
            </label>
          `).join('')}
        </div>
        <div style="margin-top:8px;">
          <div class="sp-radio-group">
            ${[
              { v: 'asc', l: '↑ Ascending' },
              { v: 'desc', l: '↓ Descending' },
            ].map(s => `
              <label class="sp-radio">
                <input type="radio" name="defaultSortDir" value="${s.v}" ${get('defaultSortDir') === s.v ? 'checked' : ''} data-key="defaultSortDir" />
                <span>${s.l}</span>
              </label>
            `).join('')}
          </div>
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Min 24h Volume</div>
        <div class="sp-radio-group">
          ${[
            { v: 0, l: 'Off' },
            { v: 10, l: '$10M+' },
            { v: 50, l: '$50M+' },
            { v: 100, l: '$100M+' },
            { v: 250, l: '$250M+' },
            { v: 500, l: '$500M+' },
          ].map(s => `
            <label class="sp-radio">
              <input type="radio" name="minVolume" value="${s.v}" ${get('minVolume') === s.v ? 'checked' : ''} data-key="minVolume" />
              <span>${s.l}</span>
            </label>
          `).join('')}
        </div>
      </div>
    `
  }

  function renderIndicatorsSection() {
    return `
      <div class="sp-section">
        <div class="sp-section-title">Open Interest</div>
        <label class="sp-toggle">
          <input type="checkbox" ${get('indicatorOI') ? 'checked' : ''} data-key="indicatorOI" />
          <span>Show OI overlay on charts</span>
        </label>
        <p class="sp-hint">Displays Open Interest as a line overlay on mini-charts and modal</p>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">OI Line Color</div>
        <div class="sp-color-row">
          <label class="sp-color-label">
            <span>Color</span>
            <input type="color" value="${get('indicatorOIColor')}" data-key="indicatorOIColor" class="sp-color-input" />
          </label>
        </div>
      </div>
      <div class="sp-section">
        <p class="sp-hint">More indicators coming soon: Funding Rate, CVD, Liquidations</p>
      </div>
    `
  }

  function renderLayoutSection() {
    const current = get('layout')
    const layouts = [
      { id: '1', label: '1', icon: '▣' },
      { id: '2h', label: '2', icon: '◫' },
      { id: '2v', label: '2V', icon: '⬒' },
      { id: '4', label: '2×2', icon: '⊞' },
      { id: '1+3', label: '1+3', icon: '◨' },
    ]
    return `
      <div class="sp-section">
        <div class="sp-section-title">Multi-Chart Layout</div>
        <div class="sp-layout-grid">
          ${layouts.map(l => `
            <button class="sp-layout-btn${current === l.id ? ' active' : ''}" data-key="layout" data-value="${l.id}" title="${l.label}">
              <span class="sp-layout-icon">${l.icon}</span>
              <span class="sp-layout-label">${l.label}</span>
            </button>
          `).join('')}
        </div>
      </div>
    `
  }

  function renderChartsSection() {
    return `
      <div class="sp-section">
        <div class="sp-section-title">Candle Type</div>
        <div class="sp-radio-group">
          ${['Candlestick', 'Bar'].map(t => `
            <label class="sp-radio">
              <input type="radio" name="candleType" value="${t}" ${get('candleType') === t ? 'checked' : ''} data-key="candleType" />
              <span>${t}</span>
            </label>
          `).join('')}
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Volume Height</div>
        <div class="sp-slider-row">
          <input type="range" min="5" max="40" value="${get('volumeHeight')}" data-key="volumeHeight" class="sp-slider" />
          <span class="sp-slider-val" data-for="volumeHeight">${get('volumeHeight')}%</span>
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Grid</div>
        <label class="sp-toggle">
          <input type="checkbox" ${get('showGrid') ? 'checked' : ''} data-key="showGrid" />
          <span>Show grid lines</span>
        </label>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Watermark</div>
        <label class="sp-toggle">
          <input type="checkbox" ${get('showWatermark') ? 'checked' : ''} data-key="showWatermark" />
          <span>Show symbol watermark</span>
        </label>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Default Timeframe</div>
        <div class="sp-radio-group">
          ${['1m', '5m', '15m', '1h', '4h', '1d'].map(t => `
            <label class="sp-radio">
              <input type="radio" name="defaultTF" value="${t}" ${get('defaultTF') === t ? 'checked' : ''} data-key="defaultTF" />
              <span>${t}</span>
            </label>
          `).join('')}
        </div>
      </div>
      <div class="sp-divider"></div>
      <div class="sp-section">
        <div class="sp-section-title">Cards per Row</div>
        <div class="sp-layout-grid">
          ${[1, 2, 3, 4, 5, 6].map(n => `
            <button class="sp-layout-btn${get('cardsPerRow') === n ? ' active' : ''}" data-key="cardsPerRow" data-value="${n}" title="${n} per row">
              <span class="sp-layout-label">${n}</span>
            </button>
          `).join('')}
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Card Size</div>
        <div class="sp-radio-group">
          ${['compact', 'normal', 'large'].map(s => `
            <label class="sp-radio">
              <input type="radio" name="cardSize" value="${s}" ${get('cardSize') === s ? 'checked' : ''} data-key="cardSize" />
              <span>${s.charAt(0).toUpperCase() + s.slice(1)}</span>
            </label>
          `).join('')}
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Sidebar Columns</div>
        <label class="sp-toggle">
          <input type="checkbox" ${get('colChg') ? 'checked' : ''} data-key="colChg" />
          <span>Chg%</span>
        </label>
        <label class="sp-toggle">
          <input type="checkbox" ${get('colNatr') ? 'checked' : ''} data-key="colNatr" />
          <span>NATR</span>
        </label>
        <label class="sp-toggle">
          <input type="checkbox" ${get('colVol') ? 'checked' : ''} data-key="colVol" />
          <span>Volume</span>
        </label>
      </div>
    `
  }

  function renderDensitiesSection() {
    return `
      <div class="sp-section">
        <label class="sp-toggle">
          <input type="checkbox" ${get('densityEnabled') ? 'checked' : ''} data-key="densityEnabled" />
          <span>Show density levels on charts</span>
        </label>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Severity Multipliers</div>
        <div class="sp-severity-row">
          <span class="sp-severity-dot large"></span>
          <span class="sp-severity-label">Large</span>
          <input type="number" value="${get('densitySeverityLarge')}" step="0.5" min="1" max="20" data-key="densitySeverityLarge" class="sp-num-input" />
          <span class="sp-severity-x">×</span>
        </div>
        <div class="sp-severity-row">
          <span class="sp-severity-dot medium"></span>
          <span class="sp-severity-label">Medium</span>
          <input type="number" value="${get('densitySeverityMedium')}" step="0.5" min="1" max="20" data-key="densitySeverityMedium" class="sp-num-input" />
          <span class="sp-severity-x">×</span>
        </div>
        <div class="sp-severity-row">
          <span class="sp-severity-dot small"></span>
          <span class="sp-severity-label">Small</span>
          <input type="number" value="${get('densitySeveritySmall')}" step="0.5" min="1" max="20" data-key="densitySeveritySmall" class="sp-num-input" />
          <span class="sp-severity-x">×</span>
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Depth from price</div>
        <div class="sp-slider-row">
          <input type="range" min="0.5" max="10" step="0.5" value="${get('densityDepthPct')}" data-key="densityDepthPct" class="sp-slider" />
          <span class="sp-slider-val" data-for="densityDepthPct">${get('densityDepthPct')}%</span>
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Min lifetime</div>
        <div class="sp-slider-row">
          <input type="range" min="1" max="60" value="${get('densityTTLMin')}" data-key="densityTTLMin" class="sp-slider" />
          <span class="sp-slider-val" data-for="densityTTLMin">${get('densityTTLMin')} min</span>
        </div>
      </div>
      <div class="sp-section">
        <div class="sp-section-title">Blacklist</div>
        <input type="text" class="sp-text-input" id="spDensityBlacklist" value="${get('densityBlacklist')}" placeholder="USDC,FDUSD,TUSD..." />
        <p class="sp-hint">Comma-separated symbols to exclude from density scan</p>
      </div>
    `
  }

  function renderSignalsSection() {
    const types = get('signalTypes') || []
    return `
      <!-- General -->
      <div class="sp-group open">
        <div class="sp-group-header" onclick="this.parentElement.classList.toggle('open')">
          <span>⚙️ General</span>
          <span class="sp-group-arrow">▶</span>
        </div>
        <div class="sp-group-body">
          <div class="sp-section">
            <div class="sp-section-title">Min Confidence</div>
            <div class="sp-slider-row">
              <input type="range" min="30" max="90" step="5" value="${get('signalMinConfidence')}" data-key="signalMinConfidence" class="sp-slider" />
              <span class="sp-slider-val" data-for="signalMinConfidence">${get('signalMinConfidence')}%</span>
            </div>
            <p class="sp-hint">Hide signals below this confidence level</p>
          </div>
          <div class="sp-section">
            <div class="sp-section-title">Alert Cooldown</div>
            <div class="sp-radio-group">
              ${[1, 5, 15, 30].map(m => `
                <label class="sp-radio">
                  <input type="radio" name="signalCooldown" value="${m}" ${get('signalCooldown') === m ? 'checked' : ''} data-key="signalCooldown" />
                  <span>${m}min</span>
                </label>
              `).join('')}
            </div>
          </div>
          <div class="sp-section">
            <label class="sp-toggle">
              <input type="checkbox" ${get('signalWatchlistOnly') ? 'checked' : ''} data-key="signalWatchlistOnly" />
              <span>Watchlist coins only</span>
            </label>
            <label class="sp-toggle" style="margin-top:6px;">
              <input type="checkbox" ${get('showSignalsOnCharts') ? 'checked' : ''} data-key="showSignalsOnCharts" />
              <span>Show signals on charts</span>
            </label>
            <p class="sp-hint">Display signal markers on mini-charts and modal</p>
          </div>
        </div>
      </div>

      <!-- Volume Spike -->
      <div class="sp-group open">
        <div class="sp-group-header" onclick="this.parentElement.classList.toggle('open')">
          <span>📊 Volume Spike</span>
          <span class="sp-group-arrow">▶</span>
        </div>
        <div class="sp-group-body">
          <div class="sp-section">
            <div class="sp-section-title">Min Ratio vs SMA(20)</div>
            <div class="sp-slider-row">
              <input type="range" min="2" max="20" step="1" value="${get('signalMinRatio')}" data-key="signalMinRatio" class="sp-slider" />
              <span class="sp-slider-val" data-for="signalMinRatio">${get('signalMinRatio')}x</span>
            </div>
          </div>
          <div class="sp-section">
            <label class="sp-toggle">
              <input type="checkbox" ${types.includes('volume_spike') ? 'checked' : ''} data-signal-type="volume_spike" />
              <span>Push notifications</span>
            </label>
          </div>
        </div>
      </div>

      <!-- OI + CVD -->
      <div class="sp-group">
        <div class="sp-group-header" onclick="this.parentElement.classList.toggle('open')">
          <span>🔮 OI + CVD</span>
          <span class="sp-group-arrow">▶</span>
        </div>
        <div class="sp-group-body">
          <div class="sp-section">
            <div class="sp-section-title">Push by sub-type</div>
            ${[
              { key: 'oi_longs', label: '🟢 Longs Build-up' },
              { key: 'oi_shorts', label: '🔴 Shorts Build-up' },
              { key: 'oi_squeeze', label: '⚡ Squeeze' },
              { key: 'oi_liquidation', label: '💥 Liquidation' },
            ].map(t => `
              <label class="sp-toggle">
                <input type="checkbox" ${types.includes(t.key) ? 'checked' : ''} data-signal-type="${t.key}" />
                <span>${t.label}</span>
              </label>
            `).join('')}
          </div>
        </div>
      </div>

      <!-- OI Divergence -->
      <div class="sp-group open">
        <div class="sp-group-header" onclick="this.parentElement.classList.toggle('open')">
          <span>🔀 OI Divergence</span>
          <span class="sp-group-arrow">▶</span>
        </div>
        <div class="sp-group-body">
          <div class="sp-section">
            <p class="sp-hint">Price/OI divergence — detects exhaustion or hidden accumulation</p>
            <label class="sp-toggle">
              <input type="checkbox" ${types.includes('oi_divergence') ? 'checked' : ''} data-signal-type="oi_divergence" />
              <span>Push notifications</span>
            </label>
          </div>
        </div>
      </div>

      <!-- Funding Squeeze -->
      <div class="sp-group open">
        <div class="sp-group-header" onclick="this.parentElement.classList.toggle('open')">
          <span>⚡ Funding Squeeze</span>
          <span class="sp-group-arrow">▶</span>
        </div>
        <div class="sp-group-body">
          <div class="sp-section">
            <p class="sp-hint">Contrarian — trades against overcrowded leverage (OI spike + extreme funding)</p>
            <label class="sp-toggle">
              <input type="checkbox" ${types.includes('oi_funding_squeeze') ? 'checked' : ''} data-signal-type="oi_funding_squeeze" />
              <span>Push notifications</span>
            </label>
          </div>
        </div>
      </div>

      <!-- Liq Sweep -->
      <div class="sp-group open">
        <div class="sp-group-header" onclick="this.parentElement.classList.toggle('open')">
          <span>🎯 Liq Sweep</span>
          <span class="sp-group-arrow">▶</span>
        </div>
        <div class="sp-group-body">
          <div class="sp-section">
            <div class="sp-section-title">Level Types</div>
            <p class="sp-hint">Which liquidity sources to show</p>
            <label class="sp-toggle">
              <input type="checkbox" ${get('sweepLevelSwing') ? 'checked' : ''} data-key="sweepLevelSwing" />
              <span>📐 Swing High/Low <span style="color:var(--text-muted);font-size:11px;">— structural levels from 1h candles</span></span>
            </label>
            <label class="sp-toggle">
              <input type="checkbox" ${get('sweepLevelWall') ? 'checked' : ''} data-key="sweepLevelWall" />
              <span>🧱 Order Book Walls <span style="color:var(--text-muted);font-size:11px;">— density walls from live orderbook</span></span>
            </label>
            <label class="sp-toggle">
              <input type="checkbox" ${get('sweepLevelRound') ? 'checked' : ''} data-key="sweepLevelRound" />
              <span>🔢 Round Numbers <span style="color:var(--text-muted);font-size:11px;">— psychological price levels</span></span>
            </label>
          </div>
          <div class="sp-section">
            <div class="sp-section-title">Min Wick Ratio</div>
            <div class="sp-slider-row">
              <input type="range" min="50" max="90" step="5" value="${get('sweepMinWickPct')}" data-key="sweepMinWickPct" class="sp-slider" />
              <span class="sp-slider-val" data-for="sweepMinWickPct">${get('sweepMinWickPct')}%</span>
            </div>
            <p class="sp-hint">Higher = cleaner pin bars only</p>
          </div>
          <div class="sp-section">
            <label class="sp-toggle">
              <input type="checkbox" ${types.includes('liq_sweep') ? 'checked' : ''} data-signal-type="liq_sweep" />
              <span>Push notifications</span>
            </label>
          </div>
        </div>
      </div>

      <!-- Channel Signal -->
      <div class="sp-group open">
        <div class="sp-group-header" onclick="this.parentElement.classList.toggle('open')">
          <span>📐 Channel Signal</span>
          <span class="sp-group-arrow">▶</span>
        </div>
        <div class="sp-group-body">
          <div class="sp-section">
            <div class="sp-section-title">Timeframes</div>
            <p class="sp-hint">Uncheck to hide from feed & push (still saved to DB for stats)</p>
            <label class="sp-toggle">
              <input type="checkbox" ${get('channelTf5m') ? 'checked' : ''} data-key="channelTf5m" />
              <span>5m <span style="color:var(--text-muted);font-size:11px;">— scan every 60s</span></span>
            </label>
            <label class="sp-toggle">
              <input type="checkbox" ${get('channelTf15m') ? 'checked' : ''} data-key="channelTf15m" />
              <span>15m <span style="color:var(--text-muted);font-size:11px;">— scan every 90s</span></span>
            </label>
            <label class="sp-toggle">
              <input type="checkbox" ${get('channelTf1h') ? 'checked' : ''} data-key="channelTf1h" />
              <span>1h <span style="color:var(--text-muted);font-size:11px;">— scan every 5min</span></span>
            </label>
          </div>
          <div class="sp-section">
            <div class="sp-section-title">Signal Sub-types</div>
            <label class="sp-toggle">
              <input type="checkbox" ${types.includes('channel') ? 'checked' : ''} data-signal-type="channel" />
              <span>Push notifications</span>
            </label>
          </div>
        </div>
      </div>

      <!-- Order-Flow -->
      <div class="sp-group open">
        <div class="sp-group-header" onclick="this.parentElement.classList.toggle('open')">
          <span>📖 Order-Flow</span>
          <span class="sp-group-arrow">▶</span>
        </div>
        <div class="sp-group-body">
          <div class="sp-section">
            <p class="sp-hint">Дисбаланс стакана (Σbid vs Σask) ≥ 35% → сигнал направления. Бумажный тикет TP +1.5% / SL −0.5% @5x.</p>
          </div>
          <div class="sp-section">
            <div class="sp-section-title">Min Confidence (только Order-Flow)</div>
            <div class="sp-slider-row">
              <input type="range" min="50" max="95" step="5" value="${get('signalOfMinConf')}" data-key="signalOfMinConf" class="sp-slider" />
              <span class="sp-slider-val" data-for="signalOfMinConf">${get('signalOfMinConf')}</span>
            </div>
            <p class="sp-hint">Confidence = сила дисбаланса: 50 ≈ ±35%, 70 ≈ ±55%, 95 ≈ ±100%. Выше = реже, но сильнее (≈300/день при 75). Не трогает общий MIN CONFIDENCE.</p>
          </div>
          <div class="sp-section">
            <label class="sp-toggle">
              <input type="checkbox" ${types.includes('orderflow_imbalance') ? 'checked' : ''} data-signal-type="orderflow_imbalance" />
              <span>Push notifications</span>
            </label>
          </div>
        </div>
      </div>

      <!-- Notifications -->
      <div class="sp-group">
        <div class="sp-group-header" onclick="this.parentElement.classList.toggle('open')">
          <span>🔔 Notifications</span>
          <span class="sp-group-arrow">▶</span>
        </div>
        <div class="sp-group-body">
          <div class="sp-section">
            <label class="sp-toggle">
              <input type="checkbox" ${get('signalNotifications') ? 'checked' : ''} data-key="signalNotifications" />
              <span>In-tab alerts</span>
            </label>
            <label class="sp-toggle">
              <input type="checkbox" ${get('signalSound') ? 'checked' : ''} data-key="signalSound" />
              <span>Sound alert</span>
            </label>
            <p class="sp-hint">Toast + browser notification when tab is open</p>
          </div>
          <div class="sp-section">
            <div class="sp-section-title">📲 Push Notifications</div>
            <label class="sp-toggle">
              <input type="checkbox" ${get('signalPush') ? 'checked' : ''} data-key="signalPush" />
              <span>Push when browser closed</span>
            </label>
            <p class="sp-hint">Server push — works on phone & with browser closed</p>
          </div>
        </div>
      </div>
    `
  }

  function renderResetSection() {
    return `
      <div class="sp-section">
        <div class="sp-section-title">Share Settings</div>
        <div style="display:flex; gap:8px;">
          <button class="sp-action-btn" id="spExportSettings" style="flex:1;">📤 Export</button>
          <button class="sp-action-btn" id="spImportSettings" style="flex:1;">📥 Import</button>
        </div>
        <p class="sp-hint">Export your settings as a code or import someone else's</p>
        <div id="spShareBox" class="sp-share-box hidden"></div>
      </div>
      <div class="sp-section">
        <button class="sp-danger-btn" id="spClearDrawings">Clear all drawings</button>
        <p class="sp-hint">Removes all lines, rays, fibs from all charts</p>
      </div>
      <div class="sp-section">
        <button class="sp-danger-btn orange" id="spResetSettings">Reset all settings</button>
        <p class="sp-hint">Restores all settings to defaults</p>
      </div>
    `
  }

  // --- Bind events for dynamic content ---
  function bindSectionEvents(container) {
    // Radio buttons
    const numericRadios = ['refreshInterval', 'minVolume', 'signalCooldown']
    container.querySelectorAll('input[type="radio"][data-key]').forEach(el => {
      el.addEventListener('change', () => {
        const val = numericRadios.includes(el.dataset.key) ? Number(el.value) : el.value
        set(el.dataset.key, val)
      })
    })

    // Checkboxes
    container.querySelectorAll('input[type="checkbox"][data-key]').forEach(el => {
      el.addEventListener('change', () => {
        set(el.dataset.key, el.checked)
        // Web Push: subscribe/unsubscribe when push toggled (separate from in-tab notifications)
        if (el.dataset.key === 'signalPush') {
          if (el.checked) {
            if (typeof subscribeToPush === 'function') subscribeToPush()
          } else {
            if (typeof unsubscribeFromPush === 'function') unsubscribeFromPush()
          }
        }
        // Channel TF toggles: re-sync push + re-render signal list
        if (el.dataset.key.startsWith('channelTf')) {
          if (typeof subscribeToPush === 'function' && get('signalPush')) subscribeToPush()
          if (typeof renderSignalList === 'function') renderSignalList()
        }
      })
    })

    // Sliders
    container.querySelectorAll('input[type="range"][data-key]').forEach(el => {
      el.addEventListener('input', () => {
        const val = parseFloat(el.value)
        set(el.dataset.key, val)
        const label = container.querySelector(`[data-for="${el.dataset.key}"]`)
        if (label) {
          const suffix = el.dataset.key.includes('Ratio') ? 'x' : el.dataset.key.includes('Pct') ? '%' : el.dataset.key.includes('TTL') ? ' min' : '%'
          label.textContent = val + suffix
        }
        // Re-sync push filters when signal settings change
        if (['signalMinRatio', 'signalMinConfidence'].includes(el.dataset.key)) {
          if (typeof subscribeToPush === 'function' && get('signalPush')) subscribeToPush()
        }
      })
    })

    // Signal type checkboxes
    container.querySelectorAll('input[data-signal-type]').forEach(el => {
      el.addEventListener('change', () => {
        const types = get('signalTypes') || []
        const t = el.dataset.signalType
        if (el.checked && !types.includes(t)) types.push(t)
        if (!el.checked) { const i = types.indexOf(t); if (i >= 0) types.splice(i, 1) }
        set('signalTypes', [...types])
        // Re-sync push subscription with updated types
        if (typeof subscribeToPush === 'function' && get('signalPush')) subscribeToPush()
      })
    })

    // Number inputs
    container.querySelectorAll('input[type="number"][data-key]').forEach(el => {
      el.addEventListener('change', () => set(el.dataset.key, parseFloat(el.value)))
    })

    // Layout buttons
    container.querySelectorAll('.sp-layout-btn[data-key]').forEach(btn => {
      btn.addEventListener('click', () => {
        // Only deactivate siblings with same key
        container.querySelectorAll(`.sp-layout-btn[data-key="${btn.dataset.key}"]`).forEach(b => b.classList.remove('active'))
        btn.classList.add('active')
        const val = btn.dataset.value
        set(btn.dataset.key, isNaN(val) ? val : parseInt(val))
      })
    })

    // Density blacklist input
    const blInput = container.querySelector('#spDensityBlacklist')
    if (blInput) {
      let blTimer = null
      blInput.addEventListener('input', () => {
        clearTimeout(blTimer)
        blTimer = setTimeout(() => set('densityBlacklist', blInput.value), 500)
      })
    }

    // Cleanup buttons
    const clearBtn = container.querySelector('#spClearDrawings')
    if (clearBtn) {
      clearBtn.addEventListener('click', () => {
        // Clear all drawings from localStorage
        const keys = Object.keys(localStorage).filter(k => k.startsWith('drawings_'))
        keys.forEach(k => localStorage.removeItem(k))
        clearBtn.textContent = 'Cleared!'
        clearBtn.disabled = true
        setTimeout(() => { clearBtn.textContent = 'Clear all drawings'; clearBtn.disabled = false }, 2000)
        notify('__clearDrawings', true)
      })
    }

    const resetBtn = container.querySelector('#spResetSettings')
    if (resetBtn) {
      resetBtn.addEventListener('click', () => {
        if (confirm('Reset all settings to defaults?')) {
          resetAll()
          resetBtn.textContent = 'Reset!'
          setTimeout(() => { resetBtn.textContent = 'Reset all settings' }, 2000)
        }
      })
    }

    // Color presets
    container.querySelectorAll('.sp-color-preset').forEach(btn => {
      btn.addEventListener('click', () => {
        container.querySelectorAll('.sp-color-preset').forEach(b => b.classList.remove('active'))
        btn.classList.add('active')
        set('candleUp', btn.dataset.up)
        set('candleDown', btn.dataset.down)
        // Update custom color inputs + preview
        const upInput = container.querySelector('[data-key="candleUp"]')
        const downInput = container.querySelector('[data-key="candleDown"]')
        if (upInput) upInput.value = btn.dataset.up
        if (downInput) downInput.value = btn.dataset.down
        updateCandlePreview(container, btn.dataset.up, btn.dataset.down)
      })
    })

    // Color inputs
    container.querySelectorAll('.sp-color-input').forEach(el => {
      el.addEventListener('input', () => {
        set(el.dataset.key, el.value)
        container.querySelectorAll('.sp-color-preset').forEach(b => b.classList.remove('active'))
        const up = get('candleUp'), down = get('candleDown')
        updateCandlePreview(container, up, down)
      })
    })

    // Watchlist remove buttons
    container.querySelectorAll('.sp-wl-remove').forEach(btn => {
      btn.addEventListener('click', () => {
        wlRemove(btn.dataset.sym)
        showToast(btn.dataset.sym.replace('USDT', '') + ' removed')
        renderActiveSection()
      })
    })

    // Watchlist only toggle
    const wlOnlyToggle = container.querySelector('#spWlOnly')
    if (wlOnlyToggle) {
      wlOnlyToggle.addEventListener('change', () => {
        set('watchlistOnly', wlOnlyToggle.checked)
        showToast(wlOnlyToggle.checked ? 'Watchlist filter ON' : 'Watchlist filter OFF')
      })
    }

    // Clear watchlist
    const clearWlBtn = container.querySelector('#spClearWatchlist')
    if (clearWlBtn) {
      clearWlBtn.addEventListener('click', () => {
        if (confirm('Clear entire watchlist?')) {
          wlClear()
          showToast('Watchlist cleared')
          renderActiveSection()
        }
      })
    }

    // Export settings
    const exportBtn = container.querySelector('#spExportSettings')
    if (exportBtn) {
      exportBtn.addEventListener('click', () => {
        const box = container.querySelector('#spShareBox')
        const data = getAll()
        const code = btoa(JSON.stringify(data))
        box.classList.remove('hidden')
        box.innerHTML = `
          <div class="sp-share-label">Your settings code:</div>
          <textarea class="sp-share-textarea" id="spShareCode" readonly rows="3">${code}</textarea>
          <button class="sp-action-btn small" id="spCopyCode">📋 Copy</button>
        `
        const copyBtn = box.querySelector('#spCopyCode')
        copyBtn.addEventListener('click', () => {
          navigator.clipboard.writeText(code).then(() => {
            copyBtn.textContent = '✅ Copied!'
            showToast('Settings code copied')
            setTimeout(() => { copyBtn.textContent = '📋 Copy' }, 2000)
          })
        })
      })
    }

    // Import settings
    const importBtn = container.querySelector('#spImportSettings')
    if (importBtn) {
      importBtn.addEventListener('click', () => {
        const box = container.querySelector('#spShareBox')
        box.classList.remove('hidden')
        box.innerHTML = `
          <div class="sp-share-label">Paste settings code:</div>
          <textarea class="sp-share-textarea" id="spPasteCode" rows="3" placeholder="Paste code here..."></textarea>
          <button class="sp-action-btn small" id="spApplyCode">✅ Apply</button>
        `
        const applyBtn = box.querySelector('#spApplyCode')
        applyBtn.addEventListener('click', () => {
          const textarea = box.querySelector('#spPasteCode')
          const code = textarea.value.trim()
          if (!code) return
          try {
            const data = JSON.parse(atob(code))
            if (typeof data !== 'object') throw new Error('bad')
            // Apply each setting
            Object.entries(data).forEach(([k, v]) => {
              if (DEFAULTS.hasOwnProperty(k)) set(k, v)
            })
            applyBtn.textContent = '🎉 Applied!'
            showToast('Settings imported successfully')
            setTimeout(() => { box.classList.add('hidden') }, 1500)
          } catch(e) {
            textarea.style.borderColor = '#ef4444'
            showToast('Invalid settings code')
          }
        })
      })
    }
  }

  // --- Open / Close ---
  function openPanel() {
    const panel = document.getElementById('settingsPanel')
    const overlay = document.getElementById('settingsOverlay')
    if (!panel) createPanel()
    document.getElementById('settingsPanel').classList.remove('hidden')
    document.getElementById('settingsOverlay').classList.remove('hidden')
    panelOpen = true
    renderActiveSection()
  }

  function closePanel() {
    const panel = document.getElementById('settingsPanel')
    const overlay = document.getElementById('settingsOverlay')
    if (panel) panel.classList.add('hidden')
    if (overlay) overlay.classList.add('hidden')
    panelOpen = false
  }

  function isOpen() { return panelOpen }

  // --- Wire up Settings button in header ---
  const settingsBtn = document.getElementById('toggleFiltersBtn')
  if (settingsBtn) {
    settingsBtn.addEventListener('click', (e) => {
      e.stopPropagation()
      openPanel()
    })
  }

  // Escape key
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && panelOpen) closePanel()
  })

  // --- Toast notification ---
  function showToast(msg) {
    let toast = document.getElementById('settingsToast')
    if (!toast) {
      toast = document.createElement('div')
      toast.id = 'settingsToast'
      toast.style.cssText = 'position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:rgba(34,197,94,0.9); color:#fff; padding:8px 20px; border-radius:8px; font-size:13px; font-weight:600; z-index:9999; opacity:0; transition:opacity 0.3s; pointer-events:none;'
      document.body.appendChild(toast)
    }
    toast.textContent = msg
    toast.style.opacity = '1'
    clearTimeout(toast._timer)
    toast._timer = setTimeout(() => { toast.style.opacity = '0' }, 2000)
  }

  // --- Init ---
  load()
  loadWatchlist()

  return {
    get,
    set,
    getAll,
    resetAll,
    onChange,
    openPanel,
    closePanel,
    isOpen,
    showToast,
    wlAdd, wlRemove, wlToggle, wlHas, wlList, wlClear,
  }
})()

📜 Git History

d91bbbdfeat(ui): separate Min Confidence slider for Order-Flow signals4 weeks ago
13b7aedfix(ui): add Order-Flow section to Signals settings (push toggle)4 weeks ago
558881ffeat: channel signal settings — TF toggles (5m/15m/1h) + push filter9 weeks ago
4b3bfdfrefactor: keep only Keltner + Regression channels, remove Parallel/Gaussian/Donchian9 weeks ago
77de754fix: channels dropdown z-index + all 5 channel types registered in settings9 weeks ago
6c58d95feat: OI Divergence + Funding Squeeze signals + OI ROC + funding gate9 weeks ago
354f8e3feat: Liquidity Sweep + Pin Bar signal detection, settings accordion UI, signal markers on charts, sort buttons9 weeks ago
ef2607dfix: sync watchlist and settings across browser tabs via storage event2 months ago
7b60335fix: handle localStorage QuotaExceededError across all files2 months ago
c391b6ffix: WS flap + JWT_SECRET + separate push/in-tab notification toggles2 months ago
Show last diff
Loading...