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
This commit is contained in:
Bu5hm4nn
2026-03-29 16:12:33 +02:00
parent 7f347fa2a6
commit 853c80d3a2

View File

@@ -107,7 +107,10 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None:
ui.label( ui.label(
"Changing the preset resets strategy templates to that preset's default comparison set." "Changing the preset resets strategy templates to that preset's default comparison set."
).classes("text-xs text-slate-500 dark:text-slate-400") ).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") loan_input = ui.number("Loan amount", value=default_loan, min=0, step=1000).classes("w-full")
ltv_input = ui.number( ltv_input = ui.number(
"Margin call LTV", "Margin call LTV",
@@ -140,14 +143,18 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None:
selected_summary.clear() selected_summary.clear()
with selected_summary: with selected_summary:
ui.label("Scenario Summary").classes("text-lg font-semibold text-slate-900 dark:text-slate-100") 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"): with ui.grid(columns=1).classes("w-full gap-4 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2"):
cards = [ cards = [
( (
"Preset", "Initial portfolio value",
preset_select_options.get(str(preset_select.value), str(preset_select.value or "")), f"${float(initial_value_input.value or 0.0):,.0f}",
), ),
("Templates", str(len(selected_template_slugs()))), ("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}"), ("Loan amount", f"${float(loan_input.value or 0.0):,.0f}"),
("Margin call LTV", f"{float(ltv_input.value or 0.0):.1%}"), ("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"), ("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() template_slugs = selected_template_slugs()
try: try:
preview_units = float(units_input.value or 0.0) # Get initial portfolio value from UI and derive entry spot
if workspace_id and config is not None and reseed_units: preview_initial_value = float(initial_value_input.value or 0.0)
preview_entry_spot = service.derive_entry_spot( preview_entry_spot = service.derive_entry_spot(
preset_slug=str(option["slug"]), preset_slug=str(option["slug"]),
template_slugs=template_slugs, template_slugs=template_slugs,
) )
preview_units = asset_quantity_from_workspace_config( # 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:
# Recalculate from workspace config
workspace_units = asset_quantity_from_workspace_config(
config, config,
entry_spot=preview_entry_spot, entry_spot=preview_entry_spot,
symbol="GLD", symbol="GLD",
) )
syncing_controls["value"] = True syncing_controls["value"] = True
try: 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: finally:
syncing_controls["value"] = False syncing_controls["value"] = False
scenario = service.preview_scenario( scenario = service.preview_scenario(
@@ -235,11 +249,11 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None:
return str(exc) return str(exc)
except Exception: except Exception:
logger.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, workspace_id,
preset_select.value, preset_select.value,
selected_template_slugs(), selected_template_slugs(),
units_input.value, initial_value_input.value,
loan_input.value, loan_input.value,
ltv_input.value, ltv_input.value,
) )
@@ -268,10 +282,23 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None:
result_panel.clear() result_panel.clear()
template_slugs = selected_template_slugs() template_slugs = selected_template_slugs()
try: 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( report = service.run_read_only_comparison(
preset_slug=str(preset_select.value or ""), preset_slug=str(preset_select.value or ""),
template_slugs=template_slugs, 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), loan_amount=float(loan_input.value or 0.0),
margin_call_ltv=float(ltv_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: except Exception:
message = "Event comparison failed. Please verify the seeded inputs and try again." message = "Event comparison failed. Please verify the seeded inputs and try again."
logger.exception( 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, workspace_id,
preset_select.value, preset_select.value,
selected_template_slugs(), selected_template_slugs(),
units_input.value, initial_value_input.value,
loan_input.value, loan_input.value,
ltv_input.value, ltv_input.value,
) )
@@ -326,6 +353,7 @@ def _render_event_comparison_page(workspace_id: str | None = None) -> None:
"Scenario dates used", "Scenario dates used",
f"{scenario.start_date.isoformat()}{scenario.end_date.isoformat()}", 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}"), ("Underlying units", f"{scenario.initial_portfolio.underlying_units:,.0f}"),
("Loan amount", f"${scenario.initial_portfolio.loan_amount:,.0f}"), ("Loan amount", f"${scenario.initial_portfolio.loan_amount:,.0f}"),
("Margin call LTV", f"{scenario.initial_portfolio.margin_call_ltv:.1%}"), ("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()) preset_select.on_value_change(lambda _: on_preset_change())
template_select.on_value_change(lambda _: on_preview_input_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()) loan_input.on_value_change(lambda _: on_preview_input_change())
ltv_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()) run_button.on_click(lambda: render_report())