Fix linting issues: line length, import sorting, unused variables
- Set ruff/black line length to 120 - Reformatted code with black - Fixed import ordering with ruff - Disabled lint for UI component files with long CSS strings - Updated pyproject.toml with proper tool configuration
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Iterator
|
||||
from typing import Any
|
||||
|
||||
from nicegui import ui
|
||||
|
||||
@@ -98,7 +99,13 @@ def option_chain() -> list[dict[str, Any]]:
|
||||
distance = (strike - spot) / spot
|
||||
for option_type in ("put", "call"):
|
||||
premium_base = 8.2 if option_type == "put" else 7.1
|
||||
premium = round(max(1.1, premium_base - abs(distance) * 18 + (0.8 if expiry == "2026-09-18" else 0.0)), 2)
|
||||
premium = round(
|
||||
max(
|
||||
1.1,
|
||||
premium_base - abs(distance) * 18 + (0.8 if expiry == "2026-09-18" else 0.0),
|
||||
),
|
||||
2,
|
||||
)
|
||||
delta = round((0.5 - distance * 1.8) * (-1 if option_type == "put" else 1), 3)
|
||||
rows.append(
|
||||
{
|
||||
@@ -115,14 +122,20 @@ def option_chain() -> list[dict[str, Any]]:
|
||||
"gamma": round(max(0.012, 0.065 - abs(distance) * 0.12), 3),
|
||||
"theta": round(-0.014 - abs(distance) * 0.025, 3),
|
||||
"vega": round(0.09 + max(0.0, 0.24 - abs(distance) * 0.6), 3),
|
||||
"rho": round((0.04 + abs(distance) * 0.09) * (-1 if option_type == "put" else 1), 3),
|
||||
"rho": round(
|
||||
(0.04 + abs(distance) * 0.09) * (-1 if option_type == "put" else 1),
|
||||
3,
|
||||
),
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def strategy_metrics(strategy_name: str, scenario_pct: int) -> dict[str, Any]:
|
||||
strategy = next((item for item in strategy_catalog() if item["name"] == strategy_name), strategy_catalog()[0])
|
||||
strategy = next(
|
||||
(item for item in strategy_catalog() if item["name"] == strategy_name),
|
||||
strategy_catalog()[0],
|
||||
)
|
||||
spot = demo_spot_price()
|
||||
floor = float(strategy.get("max_drawdown_floor", spot * 0.95))
|
||||
cap = strategy.get("upside_cap")
|
||||
@@ -157,7 +170,9 @@ def strategy_metrics(strategy_name: str, scenario_pct: int) -> dict[str, Any]:
|
||||
"strategy": strategy,
|
||||
"scenario_pct": scenario_pct,
|
||||
"scenario_price": scenario_price,
|
||||
"scenario_series": [{"price": price, "benefit": benefit} for price, benefit in zip(scenario_prices, benefits, strict=True)],
|
||||
"scenario_series": [
|
||||
{"price": price, "benefit": benefit} for price, benefit in zip(scenario_prices, benefits, strict=True)
|
||||
],
|
||||
"waterfall_steps": waterfall_steps,
|
||||
"unhedged_equity": round(unhedged_equity, 2),
|
||||
"hedged_equity": round(hedged_equity, 2),
|
||||
@@ -169,7 +184,9 @@ def dashboard_page(title: str, subtitle: str, current: str) -> Iterator[ui.colum
|
||||
ui.colors(primary="#0f172a", secondary="#1e293b", accent="#0ea5e9")
|
||||
|
||||
with ui.column().classes("mx-auto w-full max-w-7xl gap-6 bg-slate-50 p-6 dark:bg-slate-950") as container:
|
||||
with ui.header(elevated=False).classes("items-center justify-between border-b border-slate-200 bg-white/90 px-6 py-4 backdrop-blur dark:border-slate-800 dark:bg-slate-950/90"):
|
||||
with ui.header(elevated=False).classes(
|
||||
"items-center justify-between border-b border-slate-200 bg-white/90 px-6 py-4 backdrop-blur dark:border-slate-800 dark:bg-slate-950/90"
|
||||
):
|
||||
with ui.row().classes("items-center gap-3"):
|
||||
ui.icon("shield").classes("text-2xl text-sky-500")
|
||||
with ui.column().classes("gap-0"):
|
||||
@@ -178,13 +195,10 @@ def dashboard_page(title: str, subtitle: str, current: str) -> Iterator[ui.colum
|
||||
with ui.row().classes("items-center gap-2 max-sm:flex-wrap"):
|
||||
for key, href, label in NAV_ITEMS:
|
||||
active = key == current
|
||||
link_classes = (
|
||||
"rounded-lg px-4 py-2 text-sm font-medium no-underline transition "
|
||||
+ (
|
||||
"bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900"
|
||||
if active
|
||||
else "text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800"
|
||||
)
|
||||
link_classes = "rounded-lg px-4 py-2 text-sm font-medium no-underline transition " + (
|
||||
"bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900"
|
||||
if active
|
||||
else "text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800"
|
||||
)
|
||||
ui.link(label, href).classes(link_classes)
|
||||
|
||||
|
||||
@@ -2,7 +2,12 @@ from __future__ import annotations
|
||||
|
||||
from nicegui import ui
|
||||
|
||||
from app.pages.common import dashboard_page, demo_spot_price, strategy_catalog, strategy_metrics
|
||||
from app.pages.common import (
|
||||
dashboard_page,
|
||||
demo_spot_price,
|
||||
strategy_catalog,
|
||||
strategy_metrics,
|
||||
)
|
||||
|
||||
|
||||
def _cost_benefit_options(metrics: dict) -> dict:
|
||||
@@ -48,7 +53,12 @@ def _waterfall_options(metrics: dict) -> dict:
|
||||
"xAxis": {"type": "category", "data": [label for label, _ in steps]},
|
||||
"yAxis": {"type": "value", "name": "USD"},
|
||||
"series": [
|
||||
{"type": "bar", "stack": "total", "data": base, "itemStyle": {"color": "rgba(0,0,0,0)"}},
|
||||
{
|
||||
"type": "bar",
|
||||
"stack": "total",
|
||||
"data": base,
|
||||
"itemStyle": {"color": "rgba(0,0,0,0)"},
|
||||
},
|
||||
{
|
||||
"type": "bar",
|
||||
"stack": "total",
|
||||
@@ -73,19 +83,35 @@ def hedge_page() -> None:
|
||||
"hedge",
|
||||
):
|
||||
with ui.row().classes("w-full gap-6 max-lg:flex-col"):
|
||||
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.card().classes(
|
||||
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
):
|
||||
ui.label("Strategy Controls").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
|
||||
selector = ui.select(strategy_map, value=selected["strategy"], label="Strategy selector").classes("w-full")
|
||||
selector = ui.select(strategy_map, value=selected["strategy"], label="Strategy selector").classes(
|
||||
"w-full"
|
||||
)
|
||||
slider_value = ui.label("Scenario move: +0%").classes("text-sm text-slate-500 dark:text-slate-400")
|
||||
slider = ui.slider(min=-25, max=25, value=0, step=5).classes("w-full")
|
||||
ui.label(f"Current spot reference: ${demo_spot_price():,.2f}").classes("text-sm text-slate-500 dark:text-slate-400")
|
||||
ui.label(f"Current spot reference: ${demo_spot_price():,.2f}").classes(
|
||||
"text-sm text-slate-500 dark:text-slate-400"
|
||||
)
|
||||
|
||||
summary = ui.card().classes("w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900")
|
||||
summary = ui.card().classes(
|
||||
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
)
|
||||
|
||||
charts_row = ui.row().classes("w-full gap-6 max-lg:flex-col")
|
||||
with charts_row:
|
||||
cost_chart = ui.echart(_cost_benefit_options(strategy_metrics(selected["strategy"], selected["scenario_pct"]))).classes("h-96 w-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900")
|
||||
waterfall_chart = ui.echart(_waterfall_options(strategy_metrics(selected["strategy"], selected["scenario_pct"]))).classes("h-96 w-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900")
|
||||
cost_chart = ui.echart(
|
||||
_cost_benefit_options(strategy_metrics(selected["strategy"], selected["scenario_pct"]))
|
||||
).classes(
|
||||
"h-96 w-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
)
|
||||
waterfall_chart = ui.echart(
|
||||
_waterfall_options(strategy_metrics(selected["strategy"], selected["scenario_pct"]))
|
||||
).classes(
|
||||
"h-96 w-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
)
|
||||
|
||||
def render_summary() -> None:
|
||||
metrics = strategy_metrics(selected["strategy"], selected["scenario_pct"])
|
||||
@@ -101,7 +127,9 @@ def hedge_page() -> None:
|
||||
("Hedged equity", f"${metrics['hedged_equity']:,.0f}"),
|
||||
]
|
||||
for label, value in cards:
|
||||
with ui.card().classes("rounded-xl border border-slate-200 bg-slate-50 p-4 shadow-none dark:border-slate-800 dark:bg-slate-950"):
|
||||
with ui.card().classes(
|
||||
"rounded-xl border border-slate-200 bg-slate-50 p-4 shadow-none dark:border-slate-800 dark:bg-slate-950"
|
||||
):
|
||||
ui.label(label).classes("text-sm text-slate-500 dark:text-slate-400")
|
||||
ui.label(value).classes("text-2xl font-bold text-slate-900 dark:text-slate-100")
|
||||
ui.label(strategy["description"]).classes("text-sm text-slate-600 dark:text-slate-300")
|
||||
|
||||
@@ -22,14 +22,34 @@ def options_page() -> None:
|
||||
"options",
|
||||
):
|
||||
with ui.row().classes("w-full gap-6 max-lg:flex-col"):
|
||||
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.card().classes(
|
||||
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
):
|
||||
ui.label("Filters").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
|
||||
expiry_select = ui.select(expiries, value=selected_expiry["value"], label="Expiry").classes("w-full")
|
||||
min_strike = ui.number("Min strike", value=strike_range["min"], min=strike_values[0], max=strike_values[-1], step=5).classes("w-full")
|
||||
max_strike = ui.number("Max strike", value=strike_range["max"], min=strike_values[0], max=strike_values[-1], step=5).classes("w-full")
|
||||
strategy_select = ui.select([item["label"] for item in strategy_catalog()], value=selected_strategy["value"], label="Add to hedge strategy").classes("w-full")
|
||||
min_strike = ui.number(
|
||||
"Min strike",
|
||||
value=strike_range["min"],
|
||||
min=strike_values[0],
|
||||
max=strike_values[-1],
|
||||
step=5,
|
||||
).classes("w-full")
|
||||
max_strike = ui.number(
|
||||
"Max strike",
|
||||
value=strike_range["max"],
|
||||
min=strike_values[0],
|
||||
max=strike_values[-1],
|
||||
step=5,
|
||||
).classes("w-full")
|
||||
strategy_select = ui.select(
|
||||
[item["label"] for item in strategy_catalog()],
|
||||
value=selected_strategy["value"],
|
||||
label="Add to hedge strategy",
|
||||
).classes("w-full")
|
||||
|
||||
selection_card = ui.card().classes("w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900")
|
||||
selection_card = ui.card().classes(
|
||||
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
)
|
||||
|
||||
chain_table = ui.html("").classes("w-full")
|
||||
greeks = GreeksTable([])
|
||||
@@ -38,14 +58,17 @@ def options_page() -> None:
|
||||
return [
|
||||
row
|
||||
for row in chain
|
||||
if row["expiry"] == selected_expiry["value"] and strike_range["min"] <= row["strike"] <= strike_range["max"]
|
||||
if row["expiry"] == selected_expiry["value"]
|
||||
and strike_range["min"] <= row["strike"] <= strike_range["max"]
|
||||
]
|
||||
|
||||
def render_selection() -> None:
|
||||
selection_card.clear()
|
||||
with selection_card:
|
||||
ui.label("Strategy Integration").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
|
||||
ui.label(f"Target strategy: {selected_strategy['value']}").classes("text-sm text-slate-500 dark:text-slate-400")
|
||||
ui.label(f"Target strategy: {selected_strategy['value']}").classes(
|
||||
"text-sm text-slate-500 dark:text-slate-400"
|
||||
)
|
||||
if not chosen_contracts:
|
||||
ui.label("No contracts added yet.").classes("text-sm text-slate-500 dark:text-slate-400")
|
||||
return
|
||||
@@ -58,11 +81,15 @@ def options_page() -> None:
|
||||
chosen_contracts.append(contract)
|
||||
render_selection()
|
||||
greeks.set_options(chosen_contracts[-6:])
|
||||
ui.notify(f"Added {contract['symbol']} to {selected_strategy['value']}", color="positive")
|
||||
ui.notify(
|
||||
f"Added {contract['symbol']} to {selected_strategy['value']}",
|
||||
color="positive",
|
||||
)
|
||||
|
||||
def render_chain() -> None:
|
||||
rows = filtered_rows()
|
||||
chain_table.content = """
|
||||
chain_table.content = (
|
||||
"""
|
||||
<div class='overflow-x-auto rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900'>
|
||||
<table class='min-w-full'>
|
||||
<thead class='bg-slate-100 dark:bg-slate-800'>
|
||||
@@ -76,8 +103,8 @@ def options_page() -> None:
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
""" + "".join(
|
||||
f"""
|
||||
"""
|
||||
+ "".join(f"""
|
||||
<tr class='border-b border-slate-200 dark:border-slate-800'>
|
||||
<td class='px-4 py-3 font-medium text-slate-900 dark:text-slate-100'>{row['symbol']}</td>
|
||||
<td class='px-4 py-3 text-slate-600 dark:text-slate-300'>{row['type'].upper()}</td>
|
||||
@@ -86,17 +113,24 @@ def options_page() -> None:
|
||||
<td class='px-4 py-3 text-slate-600 dark:text-slate-300'>Δ {row['delta']:+.3f} · Γ {row['gamma']:.3f} · Θ {row['theta']:+.3f}</td>
|
||||
<td class='px-4 py-3 text-sky-600 dark:text-sky-300'>Use quick-add buttons below</td>
|
||||
</tr>
|
||||
"""
|
||||
for row in rows
|
||||
) + ("" if rows else "<tr><td colspan='6' class='px-4 py-6 text-center text-slate-500 dark:text-slate-400'>No contracts match the current filter.</td></tr>") + """
|
||||
""" for row in rows)
|
||||
+ (
|
||||
""
|
||||
if rows
|
||||
else "<tr><td colspan='6' class='px-4 py-6 text-center text-slate-500 dark:text-slate-400'>No contracts match the current filter.</td></tr>"
|
||||
)
|
||||
+ """
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
chain_table.update()
|
||||
quick_add.clear()
|
||||
with quick_add:
|
||||
ui.label("Quick add to hedge").classes("text-sm font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400")
|
||||
ui.label("Quick add to hedge").classes(
|
||||
"text-sm font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
|
||||
)
|
||||
with ui.row().classes("w-full gap-2 max-sm:flex-col"):
|
||||
for row in rows[:6]:
|
||||
ui.button(
|
||||
@@ -105,14 +139,19 @@ def options_page() -> None:
|
||||
).props("outline color=primary")
|
||||
greeks.set_options(rows[:6])
|
||||
|
||||
quick_add = ui.card().classes("w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900")
|
||||
quick_add = ui.card().classes(
|
||||
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
)
|
||||
|
||||
def update_filters() -> None:
|
||||
selected_expiry["value"] = expiry_select.value
|
||||
strike_range["min"] = float(min_strike.value)
|
||||
strike_range["max"] = float(max_strike.value)
|
||||
if strike_range["min"] > strike_range["max"]:
|
||||
strike_range["min"], strike_range["max"] = strike_range["max"], strike_range["min"]
|
||||
strike_range["min"], strike_range["max"] = (
|
||||
strike_range["max"],
|
||||
strike_range["min"],
|
||||
)
|
||||
min_strike.value = strike_range["min"]
|
||||
max_strike.value = strike_range["max"]
|
||||
render_chain()
|
||||
@@ -120,7 +159,12 @@ def options_page() -> None:
|
||||
expiry_select.on_value_change(lambda _: update_filters())
|
||||
min_strike.on_value_change(lambda _: update_filters())
|
||||
max_strike.on_value_change(lambda _: update_filters())
|
||||
strategy_select.on_value_change(lambda event: (selected_strategy.__setitem__("value", event.value), render_selection()))
|
||||
strategy_select.on_value_change(
|
||||
lambda event: (
|
||||
selected_strategy.__setitem__("value", event.value),
|
||||
render_selection(),
|
||||
)
|
||||
)
|
||||
|
||||
render_selection()
|
||||
render_chain()
|
||||
|
||||
@@ -3,7 +3,13 @@ from __future__ import annotations
|
||||
from nicegui import ui
|
||||
|
||||
from app.components import PortfolioOverview
|
||||
from app.pages.common import dashboard_page, portfolio_snapshot, quick_recommendations, recommendation_style, strategy_catalog
|
||||
from app.pages.common import (
|
||||
dashboard_page,
|
||||
portfolio_snapshot,
|
||||
quick_recommendations,
|
||||
recommendation_style,
|
||||
strategy_catalog,
|
||||
)
|
||||
|
||||
|
||||
@ui.page("/")
|
||||
@@ -18,13 +24,31 @@ def overview_page() -> None:
|
||||
):
|
||||
with ui.grid(columns=4).classes("w-full gap-4 max-lg:grid-cols-2 max-sm:grid-cols-1"):
|
||||
summary_cards = [
|
||||
("Spot Price", f"${portfolio['spot_price']:,.2f}", "GLD reference price"),
|
||||
("Margin Call Price", f"${portfolio['margin_call_price']:,.2f}", "Implied trigger level"),
|
||||
("Cash Buffer", f"${portfolio['cash_buffer']:,.0f}", "Available liquidity"),
|
||||
("Hedge Budget", f"${portfolio['hedge_budget']:,.0f}", "Approved premium budget"),
|
||||
(
|
||||
"Spot Price",
|
||||
f"${portfolio['spot_price']:,.2f}",
|
||||
"GLD reference price",
|
||||
),
|
||||
(
|
||||
"Margin Call Price",
|
||||
f"${portfolio['margin_call_price']:,.2f}",
|
||||
"Implied trigger level",
|
||||
),
|
||||
(
|
||||
"Cash Buffer",
|
||||
f"${portfolio['cash_buffer']:,.0f}",
|
||||
"Available liquidity",
|
||||
),
|
||||
(
|
||||
"Hedge Budget",
|
||||
f"${portfolio['hedge_budget']:,.0f}",
|
||||
"Approved premium budget",
|
||||
),
|
||||
]
|
||||
for title, value, caption in summary_cards:
|
||||
with ui.card().classes("rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"):
|
||||
with ui.card().classes(
|
||||
"rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
):
|
||||
ui.label(title).classes("text-sm font-medium text-slate-500 dark:text-slate-400")
|
||||
ui.label(value).classes("text-3xl font-bold text-slate-900 dark:text-slate-50")
|
||||
ui.label(caption).classes("text-sm text-slate-500 dark:text-slate-400")
|
||||
@@ -33,13 +57,18 @@ def overview_page() -> None:
|
||||
portfolio_view.update(portfolio)
|
||||
|
||||
with ui.row().classes("w-full gap-6 max-lg:flex-col"):
|
||||
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.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("Current LTV Status").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
|
||||
ui.label(f"Threshold {portfolio['margin_call_ltv'] * 100:.0f}%").classes(
|
||||
"rounded-full bg-rose-100 px-3 py-1 text-xs font-semibold text-rose-700 dark:bg-rose-500/15 dark:text-rose-300"
|
||||
)
|
||||
ui.linear_progress(value=portfolio["ltv_ratio"] / portfolio["margin_call_ltv"], show_value=False).props("color=warning track-color=grey-3 rounded")
|
||||
ui.linear_progress(
|
||||
value=portfolio["ltv_ratio"] / portfolio["margin_call_ltv"],
|
||||
show_value=False,
|
||||
).props("color=warning track-color=grey-3 rounded")
|
||||
ui.label(
|
||||
f"Current LTV is {portfolio['ltv_ratio'] * 100:.1f}% with a margin buffer of {(portfolio['margin_call_ltv'] - portfolio['ltv_ratio']) * 100:.1f} percentage points."
|
||||
).classes("text-sm text-slate-600 dark:text-slate-300")
|
||||
@@ -47,10 +76,14 @@ def overview_page() -> None:
|
||||
"Warning: if GLD approaches the margin-call price, collateral remediation or hedge monetization will be required."
|
||||
).classes("text-sm font-medium text-amber-700 dark:text-amber-300")
|
||||
|
||||
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.card().classes(
|
||||
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
):
|
||||
ui.label("Strategy Snapshot").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
|
||||
for strategy in strategy_catalog():
|
||||
with ui.row().classes("w-full items-start justify-between gap-4 border-b border-slate-100 py-3 last:border-b-0 dark:border-slate-800"):
|
||||
with ui.row().classes(
|
||||
"w-full items-start justify-between gap-4 border-b border-slate-100 py-3 last:border-b-0 dark:border-slate-800"
|
||||
):
|
||||
with ui.column().classes("gap-1"):
|
||||
ui.label(strategy["label"]).classes("font-semibold text-slate-900 dark:text-slate-100")
|
||||
ui.label(strategy["description"]).classes("text-sm text-slate-500 dark:text-slate-400")
|
||||
|
||||
@@ -13,31 +13,55 @@ def settings_page() -> None:
|
||||
"settings",
|
||||
):
|
||||
with ui.row().classes("w-full gap-6 max-lg:flex-col"):
|
||||
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.card().classes(
|
||||
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
):
|
||||
ui.label("Portfolio Parameters").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
|
||||
gold_value = ui.number("Gold collateral value", value=215000, min=0, step=1000).classes("w-full")
|
||||
loan_amount = ui.number("Loan amount", value=145000, min=0, step=1000).classes("w-full")
|
||||
margin_threshold = ui.number("Margin call LTV", value=0.75, min=0.1, max=0.95, step=0.01).classes("w-full")
|
||||
hedge_budget = ui.number("Monthly hedge budget", value=8000, min=0, step=500).classes("w-full")
|
||||
margin_threshold = ui.number("Margin call LTV", value=0.75, min=0.1, max=0.95, step=0.01).classes(
|
||||
"w-full"
|
||||
)
|
||||
ui.number("Monthly hedge budget", value=8000, min=0, step=500).classes("w-full")
|
||||
|
||||
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.card().classes(
|
||||
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
):
|
||||
ui.label("Data Sources").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
|
||||
primary_source = ui.select(["yfinance", "ibkr", "alpaca"], value="yfinance", label="Primary source").classes("w-full")
|
||||
fallback_source = ui.select(["fallback", "yfinance", "manual"], value="fallback", label="Fallback source").classes("w-full")
|
||||
primary_source = ui.select(
|
||||
["yfinance", "ibkr", "alpaca"],
|
||||
value="yfinance",
|
||||
label="Primary source",
|
||||
).classes("w-full")
|
||||
fallback_source = ui.select(
|
||||
["fallback", "yfinance", "manual"],
|
||||
value="fallback",
|
||||
label="Fallback source",
|
||||
).classes("w-full")
|
||||
refresh_interval = ui.number("Refresh interval (seconds)", value=5, min=1, step=1).classes("w-full")
|
||||
ui.switch("Enable Redis cache", value=True)
|
||||
|
||||
with ui.row().classes("w-full gap-6 max-lg:flex-col"):
|
||||
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.card().classes(
|
||||
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
):
|
||||
ui.label("Alert Thresholds").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
|
||||
ltv_warning = ui.number("LTV warning level", value=0.70, min=0.1, max=0.95, step=0.01).classes("w-full")
|
||||
vol_alert = ui.number("Volatility spike alert", value=0.25, min=0.01, max=2.0, step=0.01).classes("w-full")
|
||||
price_alert = ui.number("Spot drawdown alert (%)", value=7.5, min=0.1, max=50.0, step=0.5).classes("w-full")
|
||||
vol_alert = ui.number("Volatility spike alert", value=0.25, min=0.01, max=2.0, step=0.01).classes(
|
||||
"w-full"
|
||||
)
|
||||
price_alert = ui.number("Spot drawdown alert (%)", value=7.5, min=0.1, max=50.0, step=0.5).classes(
|
||||
"w-full"
|
||||
)
|
||||
email_alerts = ui.switch("Email alerts", value=False)
|
||||
|
||||
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.card().classes(
|
||||
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
):
|
||||
ui.label("Export / Import").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
|
||||
export_format = ui.select(["json", "csv", "yaml"], value="json", label="Export format").classes("w-full")
|
||||
export_format = ui.select(["json", "csv", "yaml"], value="json", label="Export format").classes(
|
||||
"w-full"
|
||||
)
|
||||
ui.switch("Include scenario history", value=True)
|
||||
ui.switch("Include option selections", value=True)
|
||||
ui.button("Import settings", icon="upload").props("outline color=primary")
|
||||
|
||||
Reference in New Issue
Block a user