from __future__ import annotations import json from typing import Any from uuid import uuid4 from nicegui import ui _CHARTS_SCRIPT_ADDED = False def _ensure_lightweight_charts_assets() -> None: global _CHARTS_SCRIPT_ADDED if _CHARTS_SCRIPT_ADDED: return ui.add_head_html( """ """ ) _CHARTS_SCRIPT_ADDED = True class CandlestickChart: """Minimal Lightweight-Charts wrapper for NiceGUI candlestick dashboards. Features: - real-time candlestick price updates - volume histogram overlay - moving-average / indicator line support """ def __init__(self, title: str = "Gold Price", *, height: int = 420) -> None: _ensure_lightweight_charts_assets() self.chart_id = f"chart_{uuid4().hex}" self.height = height with ui.card().classes("w-full rounded-2xl border border-slate-800 bg-slate-950/90 shadow-xl"): with ui.row().classes("w-full items-center justify-between"): ui.label(title).classes("text-lg font-semibold text-white") ui.label("Live").classes( "rounded-full bg-emerald-500/15 px-3 py-1 text-xs font-medium uppercase tracking-wide text-emerald-300" ) self.container = ui.html(f'
').style( f"height: {height}px;" ) self._initialize_chart() def _initialize_chart(self) -> None: ui.run_javascript( f""" (function() {{ const root = document.getElementById({json.dumps(self.chart_id)}); if (!root || typeof LightweightCharts === 'undefined') return; root.innerHTML = ''; window.vaultDashCharts = window.vaultDashCharts || {{}}; const chart = LightweightCharts.createChart(root, {{ autoSize: true, layout: {{ background: {{ color: '#020617' }}, textColor: '#cbd5e1', }}, grid: {{ vertLines: {{ color: 'rgba(148, 163, 184, 0.12)' }}, horzLines: {{ color: 'rgba(148, 163, 184, 0.12)' }}, }}, rightPriceScale: {{ borderColor: 'rgba(148, 163, 184, 0.25)' }}, timeScale: {{ borderColor: 'rgba(148, 163, 184, 0.25)' }}, crosshair: {{ mode: LightweightCharts.CrosshairMode.Normal }}, }}); const candleSeries = chart.addSeries(LightweightCharts.CandlestickSeries, {{ upColor: '#22c55e', downColor: '#ef4444', borderVisible: false, wickUpColor: '#22c55e', wickDownColor: '#ef4444', }}); const volumeSeries = chart.addSeries(LightweightCharts.HistogramSeries, {{ priceFormat: {{ type: 'volume' }}, priceScaleId: '', scaleMargins: {{ top: 0.78, bottom: 0 }}, color: 'rgba(56, 189, 248, 0.45)', }}); window.vaultDashCharts[{json.dumps(self.chart_id)}] = {{ chart, candleSeries, volumeSeries, indicators: {{}}, }}; }})(); """ ) def set_candles(self, candles: list[dict[str, Any]]) -> None: payload = json.dumps(candles) ui.run_javascript( f""" (function() {{ const ref = window.vaultDashCharts?.[{json.dumps(self.chart_id)}]; if (!ref) return; ref.candleSeries.setData({payload}); ref.chart.timeScale().fitContent(); }})(); """ ) def update_price(self, candle: dict[str, Any]) -> None: payload = json.dumps(candle) ui.run_javascript( f""" (function() {{ const ref = window.vaultDashCharts?.[{json.dumps(self.chart_id)}]; if (!ref) return; ref.candleSeries.update({payload}); }})(); """ ) def set_volume(self, volume_points: list[dict[str, Any]]) -> None: payload = json.dumps(volume_points) ui.run_javascript( f""" (function() {{ const ref = window.vaultDashCharts?.[{json.dumps(self.chart_id)}]; if (!ref) return; ref.volumeSeries.setData({payload}); }})(); """ ) def update_volume(self, volume_point: dict[str, Any]) -> None: payload = json.dumps(volume_point) ui.run_javascript( f""" (function() {{ const ref = window.vaultDashCharts?.[{json.dumps(self.chart_id)}]; if (!ref) return; ref.volumeSeries.update({payload}); }})(); """ ) def set_indicator( self, name: str, points: list[dict[str, Any]], *, color: str = "#f59e0b", line_width: int = 2, ) -> None: key = json.dumps(name) payload = json.dumps(points) ui.run_javascript( f""" (function() {{ const ref = window.vaultDashCharts?.[{json.dumps(self.chart_id)}]; if (!ref) return; if (!ref.indicators[{key}]) {{ ref.indicators[{key}] = ref.chart.addSeries(LightweightCharts.LineSeries, {{ color: {json.dumps(color)}, lineWidth: {line_width}, priceLineVisible: false, lastValueVisible: true, }}); }} ref.indicators[{key}].setData({payload}); }})(); """ ) def update_indicator(self, name: str, point: dict[str, Any]) -> None: key = json.dumps(name) payload = json.dumps(point) ui.run_javascript( f""" (function() {{ const ref = window.vaultDashCharts?.[{json.dumps(self.chart_id)}]; const series = ref?.indicators?.[{key}]; if (!series) return; series.update({payload}); }})(); """ )