from __future__ import annotations
from typing import Any
from nicegui import ui
from app.models.option import OptionContract
class GreeksTable:
"""Live Greeks table with simple risk-level color coding."""
def __init__(self, options: list[OptionContract | dict[str, Any]] | None = None) -> None:
self.options = options or []
with ui.card().classes(
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
):
with ui.row().classes("w-full items-center justify-between"):
ui.label("Option Greeks").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
ui.label("Live Risk Snapshot").classes(
"rounded-full bg-violet-100 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-violet-700 dark:bg-violet-500/15 dark:text-violet-300"
)
self.table_html = ui.html("").classes("w-full")
self.set_options(self.options)
def set_options(self, options: list[OptionContract | dict[str, Any]]) -> None:
self.options = options
self.table_html.content = self._render_table()
self.table_html.update()
def _render_table(self) -> str:
rows = [self._row_html(option) for option in self.options]
return f"""
| Option |
Delta |
Gamma |
Theta |
Vega |
Rho |
{''.join(rows) if rows else self._empty_row()}
"""
def _row_html(self, option: OptionContract | dict[str, Any]) -> str:
if isinstance(option, OptionContract):
label = f"{option.option_type.upper()} {option.strike:.2f}"
greeks = {
"delta": option.greeks.delta,
"gamma": option.greeks.gamma,
"theta": option.greeks.theta,
"vega": option.greeks.vega,
"rho": option.greeks.rho,
}
else:
label = str(option.get("label") or option.get("symbol") or option.get("name") or "Option")
greeks = {
greek: float(option.get(greek, option.get("greeks", {}).get(greek, 0.0)))
for greek in ("delta", "gamma", "theta", "vega", "rho")
}
cells = "".join(
f'{value:+.4f} | '
for name, value in greeks.items()
)
return (
''
f'| {label} | '
f"{cells}"
"
"
)
@staticmethod
def _risk_class(name: str, value: float) -> str:
magnitude = abs(value)
if name == "gamma":
if magnitude >= 0.08:
return "text-rose-600 dark:text-rose-400"
if magnitude >= 0.04:
return "text-amber-600 dark:text-amber-400"
return "text-emerald-600 dark:text-emerald-400"
if name == "theta":
if value <= -0.08:
return "text-rose-600 dark:text-rose-400"
if value <= -0.03:
return "text-amber-600 dark:text-amber-400"
return "text-emerald-600 dark:text-emerald-400"
if magnitude >= 0.6:
return "text-rose-600 dark:text-rose-400"
if magnitude >= 0.3:
return "text-amber-600 dark:text-amber-400"
return "text-emerald-600 dark:text-emerald-400"
@staticmethod
def _empty_row() -> str:
return (
'| '
"No options selected"
" |
"
)