- Add -r requirements.txt to requirements-dev.txt - Fix mypy errors: - Remove slots=True from Settings dataclass - Add explicit list[float] type annotations in hedge.py - Add type ignore comments for optional QuantLib imports - Use Sequence instead of list in GreeksTable for covariance - Fix dict type annotation in options.py - Add type ignore for nicegui attr-defined errors - Disable attr-defined error code in mypy config
107 lines
4.8 KiB
Python
107 lines
4.8 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, Sequence
|
|
|
|
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: Sequence[OptionContract | dict[str, Any]] | None = None) -> None:
|
|
self.options: Sequence[OptionContract | dict[str, Any]] = 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: Sequence[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"""
|
|
<div class=\"overflow-x-auto\">
|
|
<table class=\"min-w-full\">
|
|
<thead class=\"bg-slate-100 dark:bg-slate-800\">
|
|
<tr>
|
|
<th class=\"px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-300\">Option</th>
|
|
<th class=\"px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-300\">Delta</th>
|
|
<th class=\"px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-300\">Gamma</th>
|
|
<th class=\"px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-300\">Theta</th>
|
|
<th class=\"px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-300\">Vega</th>
|
|
<th class=\"px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-300\">Rho</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>{''.join(rows) if rows else self._empty_row()}</tbody>
|
|
</table>
|
|
</div>
|
|
"""
|
|
|
|
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'<td class="px-4 py-3 font-semibold {self._risk_class(name, value)}">{value:+.4f}</td>'
|
|
for name, value in greeks.items()
|
|
)
|
|
return (
|
|
'<tr class="border-b border-slate-200 dark:border-slate-800">'
|
|
f'<td class="px-4 py-3 font-medium text-slate-900 dark:text-slate-100">{label}</td>'
|
|
f"{cells}"
|
|
"</tr>"
|
|
)
|
|
|
|
@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 (
|
|
'<tr><td colspan="6" class="px-4 py-6 text-center text-slate-500 dark:text-slate-400">'
|
|
"No options selected"
|
|
"</td></tr>"
|
|
)
|