From 853c80d3a2f623f0105b81b9a1a8b3294a5eea69 Mon Sep 17 00:00:00 2001 From: Bu5hm4nn Date: Sun, 29 Mar 2026 16:12:33 +0200 Subject: [PATCH] feat(event-comparison): use initial portfolio value instead of underlying units - Changed UI input from 'Underlying units' to 'Initial portfolio value ($)' - Underlying units are now calculated as initial_value / entry_spot - Updated default value to workspace gold_value instead of gold_ounces * entry_spot - Result summary now shows both 'Initial value' and 'Underlying units' - This allows users to specify how much they invest on day 1, and the system automatically calculates the maximum purchasable shares/contracts --- app/pages/event_comparison.py | 62 +++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/app/pages/event_comparison.py b/app/pages/event_comparison.py index fb437fb..0f23bc0 100644 --- a/app/pages/event_comparison.py +++ b/app/pages/event_comparison.py @@ -107,7 +107,10 @@ 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") - units_input = ui.number("Underlying units", value=default_units, min=0.0001, step=1).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", @@ -140,14 +143,18 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: selected_summary.clear() with selected_summary: ui.label("Scenario Summary").classes("text-lg font-semibold text-slate-900 dark:text-slate-100") + # Calculate underlying units from initial value and entry spot + computed_units = 0.0 + if entry_spot is not None and entry_spot > 0: + computed_units = float(initial_value_input.value or 0.0) / entry_spot with ui.grid(columns=1).classes("w-full gap-4 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2"): cards = [ ( - "Preset", - preset_select_options.get(str(preset_select.value), str(preset_select.value or "โ€”")), + "Initial portfolio value", + f"${float(initial_value_input.value or 0.0):,.0f}", ), ("Templates", str(len(selected_template_slugs()))), - ("Underlying units", f"{float(units_input.value or 0.0):,.0f}"), + ("Underlying units", f"{computed_units:,.0f}" if computed_units > 0 else "โ€”"), ("Loan amount", f"${float(loan_input.value or 0.0):,.0f}"), ("Margin call LTV", f"{float(ltv_input.value or 0.0):.1%}"), ("Entry spot", f"${entry_spot:,.2f}" if entry_spot is not None else "Unavailable"), @@ -205,20 +212,27 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: template_slugs = selected_template_slugs() try: - preview_units = float(units_input.value or 0.0) + # Get initial portfolio value from UI and derive entry spot + preview_initial_value = float(initial_value_input.value or 0.0) + preview_entry_spot = service.derive_entry_spot( + preset_slug=str(option["slug"]), + template_slugs=template_slugs, + ) + # Calculate underlying units from initial value and entry spot + preview_units = preview_initial_value / preview_entry_spot if preview_entry_spot > 0 else 0.0 + if workspace_id and config is not None and reseed_units: - preview_entry_spot = service.derive_entry_spot( - preset_slug=str(option["slug"]), - template_slugs=template_slugs, - ) - preview_units = asset_quantity_from_workspace_config( + # Recalculate from workspace config + workspace_units = asset_quantity_from_workspace_config( config, entry_spot=preview_entry_spot, symbol="GLD", ) syncing_controls["value"] = True try: - units_input.value = preview_units + initial_value_input.value = workspace_units * preview_entry_spot + preview_units = workspace_units + preview_initial_value = workspace_units * preview_entry_spot finally: syncing_controls["value"] = False scenario = service.preview_scenario( @@ -235,11 +249,11 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: return str(exc) except Exception: logger.exception( - "Event comparison preview failed for workspace=%s preset=%s templates=%s units=%s loan=%s margin_call_ltv=%s", + "Event comparison preview failed for workspace=%s preset=%s templates=%s initial_value=%s loan=%s margin_call_ltv=%s", workspace_id, preset_select.value, selected_template_slugs(), - units_input.value, + initial_value_input.value, loan_input.value, ltv_input.value, ) @@ -268,10 +282,23 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: result_panel.clear() template_slugs = selected_template_slugs() try: + # Get initial portfolio value and calculate underlying units + initial_value = float(initial_value_input.value or 0.0) + # Get entry spot from preview + option = preset_lookup.get(str(preset_select.value or "")) + if option is None: + validation_label.set_text("Select a preset to run comparison.") + return + entry_spot = service.derive_entry_spot( + preset_slug=str(option["slug"]), + template_slugs=template_slugs, + ) + underlying_units = initial_value / entry_spot if entry_spot > 0 else 0.0 + report = service.run_read_only_comparison( preset_slug=str(preset_select.value or ""), template_slugs=template_slugs, - underlying_units=float(units_input.value or 0.0), + underlying_units=underlying_units, loan_amount=float(loan_input.value or 0.0), margin_call_ltv=float(ltv_input.value or 0.0), ) @@ -282,11 +309,11 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: except Exception: message = "Event comparison failed. Please verify the seeded inputs and try again." logger.exception( - "Event comparison page run failed for workspace=%s preset=%s templates=%s units=%s loan=%s margin_call_ltv=%s", + "Event comparison page run failed for workspace=%s preset=%s templates=%s initial_value=%s loan=%s margin_call_ltv=%s", workspace_id, preset_select.value, selected_template_slugs(), - units_input.value, + initial_value_input.value, loan_input.value, ltv_input.value, ) @@ -326,6 +353,7 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: "Scenario dates used", f"{scenario.start_date.isoformat()} โ†’ {scenario.end_date.isoformat()}", ), + ("Initial value", f"${float(initial_value_input.value or 0.0):,.0f}"), ("Underlying units", f"{scenario.initial_portfolio.underlying_units:,.0f}"), ("Loan amount", f"${scenario.initial_portfolio.loan_amount:,.0f}"), ("Margin call LTV", f"{scenario.initial_portfolio.margin_call_ltv:.1%}"), @@ -553,7 +581,7 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None: preset_select.on_value_change(lambda _: on_preset_change()) template_select.on_value_change(lambda _: on_preview_input_change()) - units_input.on_value_change(lambda _: on_preview_input_change()) + initial_value_input.on_value_change(lambda _: on_preview_input_change()) loan_input.on_value_change(lambda _: on_preview_input_change()) ltv_input.on_value_change(lambda _: on_preview_input_change()) run_button.on_click(lambda: render_report())