Initial commit: Vault Dashboard for options hedging
- FastAPI + NiceGUI web application - QuantLib-based Black-Scholes pricing with Greeks - Protective put, laddered, and LEAPS strategies - Real-time WebSocket updates - TradingView-style charts via Lightweight-Charts - Docker containerization - GitLab CI/CD pipeline for VPS deployment - VPN-only access configuration
This commit is contained in:
182
app/components/charts.py
Normal file
182
app/components/charts.py
Normal file
@@ -0,0 +1,182 @@
|
||||
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(
|
||||
"""
|
||||
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
|
||||
"""
|
||||
)
|
||||
_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'<div id="{self.chart_id}" class="w-full rounded-xl"></div>').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});
|
||||
}})();
|
||||
"""
|
||||
)
|
||||
Reference in New Issue
Block a user