diff --git a/app/components/charts.py b/app/components/charts.py
index aef4fca..41d945b 100644
--- a/app/components/charts.py
+++ b/app/components/charts.py
@@ -13,9 +13,11 @@ def _ensure_lightweight_charts_assets() -> None:
global _CHARTS_SCRIPT_ADDED
if _CHARTS_SCRIPT_ADDED:
return
- ui.add_head_html("""
+ ui.add_head_html(
+ """
- """)
+ """
+ )
_CHARTS_SCRIPT_ADDED = True
@@ -46,7 +48,8 @@ class CandlestickChart:
self._initialize_chart()
def _initialize_chart(self) -> None:
- ui.run_javascript(f"""
+ ui.run_javascript(
+ f"""
(function() {{
const root = document.getElementById({json.dumps(self.chart_id)});
if (!root || typeof LightweightCharts === 'undefined') return;
@@ -91,48 +94,57 @@ class CandlestickChart:
indicators: {{}},
}};
}})();
- """)
+ """
+ )
def set_candles(self, candles: list[dict[str, Any]]) -> None:
payload = json.dumps(candles)
- ui.run_javascript(f"""
+ 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"""
+ 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"""
+ 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"""
+ 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,
@@ -144,7 +156,8 @@ class CandlestickChart:
) -> None:
key = json.dumps(name)
payload = json.dumps(points)
- ui.run_javascript(f"""
+ ui.run_javascript(
+ f"""
(function() {{
const ref = window.vaultDashCharts?.[{json.dumps(self.chart_id)}];
if (!ref) return;
@@ -158,16 +171,19 @@ class CandlestickChart:
}}
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"""
+ 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});
}})();
- """)
+ """
+ )
diff --git a/app/components/strategy_panel.py b/app/components/strategy_panel.py
index e1cb165..35f66a3 100644
--- a/app/components/strategy_panel.py
+++ b/app/components/strategy_panel.py
@@ -117,7 +117,8 @@ class StrategyComparisonPanel:
scenario_class = (
"text-emerald-600 dark:text-emerald-400" if scenario >= 0 else "text-rose-600 dark:text-rose-400"
)
- rows.append(f"""
+ rows.append(
+ f"""
| {name} |
${cost:,.2f} |
@@ -125,7 +126,8 @@ class StrategyComparisonPanel:
{self._format_cap(strategy)} |
${scenario:,.2f} |
- """)
+ """
+ )
return f"""
diff --git a/app/pages/event_comparison.py b/app/pages/event_comparison.py
index cc2cc78..06de039 100644
--- a/app/pages/event_comparison.py
+++ b/app/pages/event_comparison.py
@@ -119,10 +119,12 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None:
ui.label(
"Changing the preset resets strategy templates to that preset's default comparison set."
).classes("text-xs text-slate-500 dark:text-slate-400")
- ui.label(
- "Underlying units will be calculated from initial value ÷ entry spot."
- ).classes("text-xs text-slate-500 dark:text-slate-400")
- initial_value_input = ui.number("Initial portfolio value ($)", value=default_units * default_entry_spot, min=0.01, step=1000).classes("w-full")
+ ui.label("Underlying units will be calculated from initial value ÷ entry spot.").classes(
+ "text-xs text-slate-500 dark:text-slate-400"
+ )
+ initial_value_input = ui.number(
+ "Initial portfolio value ($)", value=default_units * default_entry_spot, min=0.01, step=1000
+ ).classes("w-full")
loan_input = ui.number("Loan amount", value=default_loan, min=0, step=1000).classes("w-full")
ltv_input = ui.number(
"Margin call LTV",
@@ -183,7 +185,11 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None:
# Show validation errors (units_error takes priority, then entry_spot_error)
display_error = units_error or entry_spot_error
if display_error:
- tone_class = "text-rose-600 dark:text-rose-300" if "must be positive" in display_error else "text-amber-700 dark:text-amber-300"
+ tone_class = (
+ "text-rose-600 dark:text-rose-300"
+ if "must be positive" in display_error
+ else "text-amber-700 dark:text-amber-300"
+ )
ui.label(display_error).classes(f"text-sm {tone_class}")
def render_result_state(title: str, message: str, *, tone: str = "info") -> None:
@@ -243,7 +249,7 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None:
scenario_label.set_text(units_error)
render_selected_summary(entry_spot=preview_entry_spot, entry_spot_error=units_error)
return units_error
-
+
if workspace_id and config is not None and reseed_units:
# Recalculate from workspace config
workspace_units = asset_quantity_from_workspace_config(
@@ -322,7 +328,7 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None:
validation_label.set_text(units_error)
render_result_state("Input validation failed", units_error, tone="warning")
return
-
+
report = service.run_read_only_comparison(
preset_slug=str(preset_select.value or ""),
template_slugs=template_slugs,
diff --git a/app/pages/hedge.py b/app/pages/hedge.py
index cc4e56d..213afcb 100644
--- a/app/pages/hedge.py
+++ b/app/pages/hedge.py
@@ -88,9 +88,7 @@ async def _resolve_hedge_spot(workspace_id: str | None = None) -> tuple[dict[str
data_service = get_data_service()
underlying = config.underlying or "GLD"
quote = await data_service.get_quote(underlying)
- spot, source, updated_at = resolve_portfolio_spot_from_quote(
- config, quote, fallback_symbol=underlying
- )
+ spot, source, updated_at = resolve_portfolio_spot_from_quote(config, quote, fallback_symbol=underlying)
portfolio = portfolio_snapshot(config, runtime_spot_price=spot)
return portfolio, source, updated_at
except Exception as exc:
@@ -114,14 +112,14 @@ async def _render_hedge_page(workspace_id: str | None = None) -> None:
}
display_mode = portfolio.get("display_mode", "XAU")
-
+
if display_mode == "GLD":
spot_unit = "/share"
spot_desc = "GLD share price"
else:
spot_unit = "/oz"
spot_desc = "converted collateral spot"
-
+
if quote_source == "configured_entry_price":
spot_label = f"Current spot reference: ${portfolio['spot_price']:,.2f}{spot_unit} (configured entry price)"
else:
@@ -251,7 +249,7 @@ async def _render_hedge_page(workspace_id: str | None = None) -> None:
def render_summary() -> None:
metrics = strategy_metrics(selected["strategy"], selected["scenario_pct"], portfolio=portfolio)
strategy = metrics["strategy"]
-
+
# Display mode-aware labels
if display_mode == "GLD":
weight_unit = "shares"
@@ -261,7 +259,7 @@ async def _render_hedge_page(workspace_id: str | None = None) -> None:
weight_unit = "oz"
price_unit = "/oz"
hedge_cost_unit = "/oz"
-
+
summary.clear()
with summary:
ui.label("Scenario Summary").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
diff --git a/app/pages/options.py b/app/pages/options.py
index a3dd677..444a4cd 100644
--- a/app/pages/options.py
+++ b/app/pages/options.py
@@ -88,9 +88,7 @@ async def options_page() -> None:
def filtered_rows() -> list[dict[str, Any]]:
return [
- row
- for row in chain_state["rows"]
- if strike_range["min"] <= float(row["strike"]) <= strike_range["max"]
+ row for row in chain_state["rows"] if strike_range["min"] <= float(row["strike"]) <= strike_range["max"]
]
def render_selection() -> None:
@@ -186,7 +184,9 @@ async def options_page() -> None:
next_chain = await data_service.get_options_chain_for_expiry("GLD", expiry)
chain_state["data"] = next_chain
- chain_state["rows"] = list(next_chain.get("rows") or [*next_chain.get("calls", []), *next_chain.get("puts", [])])
+ chain_state["rows"] = list(
+ next_chain.get("rows") or [*next_chain.get("calls", []), *next_chain.get("puts", [])]
+ )
min_value, max_value = strike_bounds(chain_state["rows"])
strike_range["min"] = min_value
diff --git a/app/pages/overview.py b/app/pages/overview.py
index 926bda0..666881f 100644
--- a/app/pages/overview.py
+++ b/app/pages/overview.py
@@ -124,11 +124,13 @@ def welcome_page(request: Request):
if turnstile.uses_test_keys
else ""
)
- ui.html(f"""""")
+ """
+ )
ui.label("You can always create a fresh workspace later if a link is lost.").classes(
"text-sm text-slate-500 dark:text-slate-400"
)
@@ -174,7 +176,11 @@ async def overview_page(workspace_id: str) -> None:
current_values[str(pos.id)] = pos.entry_value
total_annual_storage_cost = calculate_total_storage_cost(positions, current_values)
portfolio["annual_storage_cost"] = float(total_annual_storage_cost)
- portfolio["storage_cost_pct"] = (float(total_annual_storage_cost) / float(portfolio["gold_value"]) * 100) if portfolio["gold_value"] > 0 else 0.0
+ portfolio["storage_cost_pct"] = (
+ (float(total_annual_storage_cost) / float(portfolio["gold_value"]) * 100)
+ if portfolio["gold_value"] > 0
+ else 0.0
+ )
alert_status = AlertService().evaluate(config, portfolio)
ltv_history_service = LtvHistoryService(repository=LtvHistoryRepository(base_path=repo.base_path))
@@ -197,7 +203,7 @@ async def overview_page(workspace_id: str) -> None:
ltv_history_csv = ""
ltv_history_notice = "Historical LTV is temporarily unavailable due to a storage error."
display_mode = portfolio.get("display_mode", "XAU")
-
+
if portfolio["quote_source"] == "configured_entry_price":
if display_mode == "GLD":
quote_status = "Live quote source: configured entry price fallback (GLD shares) · Last updated Unavailable"
@@ -342,7 +348,7 @@ async def overview_page(workspace_id: str) -> None:
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
):
ui.label("Portfolio Snapshot").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
-
+
# Display mode-aware labels
if display_mode == "GLD":
spot_label = "GLD Share Price"
@@ -352,7 +358,7 @@ async def overview_page(workspace_id: str) -> None:
spot_label = "Collateral Spot Price"
spot_unit = "/oz"
margin_label = "Margin Call Price"
-
+
with ui.grid(columns=1).classes("w-full gap-4 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2"):
summary_cards = [
(
diff --git a/app/pages/settings.py b/app/pages/settings.py
index b52e4dc..d9641f8 100644
--- a/app/pages/settings.py
+++ b/app/pages/settings.py
@@ -263,9 +263,9 @@ def settings_page(workspace_id: str) -> None:
value=config.display_mode,
label="Display mode",
).classes("w-full")
-
+
ui.separator().classes("my-4")
-
+
ui.label("Data Sources").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
underlying = ui.select(
{
@@ -384,8 +384,12 @@ def settings_page(workspace_id: str) -> None:
).classes("w-full")
ui.separator().classes("my-3")
- ui.label("Storage Costs (optional)").classes("text-sm font-semibold text-slate-700 dark:text-slate-300")
- ui.label("For physical gold (XAU), defaults to 0.12% annual vault storage.").classes("text-xs text-slate-500 dark:text-slate-400 mb-2")
+ ui.label("Storage Costs (optional)").classes(
+ "text-sm font-semibold text-slate-700 dark:text-slate-300"
+ )
+ ui.label("For physical gold (XAU), defaults to 0.12% annual vault storage.").classes(
+ "text-xs text-slate-500 dark:text-slate-400 mb-2"
+ )
pos_storage_cost_basis = ui.number(
"Storage cost (% per year or fixed $)",
@@ -401,8 +405,12 @@ def settings_page(workspace_id: str) -> None:
).classes("w-full")
ui.separator().classes("my-3")
- ui.label("Premium & Spread (optional)").classes("text-sm font-semibold text-slate-700 dark:text-slate-300")
- ui.label("For physical gold, accounts for dealer markup and bid/ask spread.").classes("text-xs text-slate-500 dark:text-slate-400 mb-2")
+ ui.label("Premium & Spread (optional)").classes(
+ "text-sm font-semibold text-slate-700 dark:text-slate-300"
+ )
+ ui.label("For physical gold, accounts for dealer markup and bid/ask spread.").classes(
+ "text-xs text-slate-500 dark:text-slate-400 mb-2"
+ )
pos_purchase_premium = ui.number(
"Purchase premium over spot (%)",
@@ -429,10 +437,14 @@ def settings_page(workspace_id: str) -> None:
try:
underlying = str(pos_underlying.value)
storage_cost_basis_val = float(pos_storage_cost_basis.value)
- storage_cost_basis = Decimal(str(storage_cost_basis_val)) if storage_cost_basis_val > 0 else None
+ storage_cost_basis = (
+ Decimal(str(storage_cost_basis_val)) if storage_cost_basis_val > 0 else None
+ )
storage_cost_period = str(pos_storage_cost_period.value) if storage_cost_basis else None
purchase_premium_val = float(pos_purchase_premium.value)
- purchase_premium = Decimal(str(purchase_premium_val / 100)) if purchase_premium_val > 0 else None
+ purchase_premium = (
+ Decimal(str(purchase_premium_val / 100)) if purchase_premium_val > 0 else None
+ )
bid_ask_spread_val = float(pos_bid_ask_spread.value)
bid_ask_spread = Decimal(str(bid_ask_spread_val / 100)) if bid_ask_spread_val > 0 else None
diff --git a/pyproject.toml b/pyproject.toml
index 2d2c332..e0303ea 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,6 @@ requires-python = ">=3.11"
[tool.ruff]
line-length = 120
-exclude = ["app/components/*.py", "app/pages/*.py"]
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I"]
@@ -14,12 +13,6 @@ select = ["E4", "E7", "E9", "F", "I"]
[tool.black]
line-length = 120
target-version = ["py312"]
-extend-exclude = '''
-/(
- app/components
- | app/pages
-)/
-'''
[tool.mypy]
ignore_missing_imports = true
diff --git a/tests/test_overview_ltv_history_playwright.py b/tests/test_overview_ltv_history_playwright.py
index 2a6d4a8..54caf26 100644
--- a/tests/test_overview_ltv_history_playwright.py
+++ b/tests/test_overview_ltv_history_playwright.py
@@ -26,14 +26,16 @@ def test_overview_shows_ltv_history_and_exports_csv() -> None:
expect(page.locator("text=90 Day").first).to_be_visible(timeout=15000)
expect(page.get_by_role("button", name="Export CSV")).to_be_visible(timeout=15000)
- series_names = page.evaluate("""
+ series_names = page.evaluate(
+ """
async () => {
const importMap = JSON.parse(document.querySelector('script[type="importmap"]').textContent).imports;
const mod = await import(importMap['nicegui-echart']);
const chart = mod.echarts.getInstanceByDom(document.querySelector('.nicegui-echart'));
return chart ? chart.getOption().series.map(series => series.name) : [];
}
- """)
+ """
+ )
assert series_names == ["LTV", "Margin threshold"]
with page.expect_download() as download_info: