From 22415491a6d2c8d7a60874cc67b7af03e0a33e0d Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Thu, 11 Jun 2026 04:14:05 +0800 Subject: [PATCH] Fix managed portfolio equity --- application/runtime_broker_adapters.py | 14 +++++++++--- tests/test_runtime_broker_adapters.py | 30 ++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/application/runtime_broker_adapters.py b/application/runtime_broker_adapters.py index 81e84cc..715fd88 100644 --- a/application/runtime_broker_adapters.py +++ b/application/runtime_broker_adapters.py @@ -89,16 +89,23 @@ def _resolve_total_equity( cash_balance: float | None, buying_power: float | None, position_market_value: float, + prefer_cash_plus_positions: bool = False, ) -> tuple[float, str]: + resolved_cash = _positive_or_none(cash_balance) + if resolved_cash is not None: + combined_value = resolved_cash + max(0.0, float(position_market_value)) + if prefer_cash_plus_positions and combined_value > 0.0: + return combined_value, "cash_plus_positions" + else: + combined_value = None + balance_total = _positive_or_none( _first_numeric_by_keyword_groups(balances, _TOTAL_EQUITY_KEYWORD_GROUPS) ) if balance_total is not None: return balance_total, "balance_total" - resolved_cash = _positive_or_none(cash_balance) - if resolved_cash is not None: - combined_value = resolved_cash + max(0.0, float(position_market_value)) + if combined_value is not None: if combined_value > 0.0: return combined_value, "cash_plus_positions" @@ -257,6 +264,7 @@ def build_portfolio_snapshot(self) -> PortfolioSnapshot: cash_balance=cash_balance, buying_power=buying_power, position_market_value=position_market_value, + prefer_cash_plus_positions=bool(managed), ) return PortfolioSnapshot( as_of=self.clock(), diff --git a/tests/test_runtime_broker_adapters.py b/tests/test_runtime_broker_adapters.py index cd17068..4a0bfae 100644 --- a/tests/test_runtime_broker_adapters.py +++ b/tests/test_runtime_broker_adapters.py @@ -43,9 +43,10 @@ def test_runtime_adapters_build_quote_and_portfolio_ports(): portfolio = adapters.build_portfolio_port().get_portfolio_snapshot() assert quote.last_price == 10.5 - assert portfolio.total_equity == 120.0 + assert portfolio.total_equity == 41.0 assert portfolio.cash_balance == 20.0 assert portfolio.positions[0].symbol == "SPY" + assert portfolio.metadata["total_equity_source"] == "cash_plus_positions" def test_portfolio_snapshot_uses_account_value_balance_key(): @@ -56,7 +57,6 @@ def get_balances(self, _account): adapters = build_runtime_broker_adapters( client=AccountValueClient(), account="12345678", - strategy_symbols=("SPY",), ) portfolio = adapters.build_portfolio_port().get_portfolio_snapshot() @@ -66,6 +66,32 @@ def get_balances(self, _account): assert portfolio.metadata["total_equity_source"] == "balance_total" +def test_managed_portfolio_snapshot_ignores_full_account_value_balance_key(): + class AccountValueClient(FakeClient): + def get_balances(self, _account): + return {"account_value": "$1,234.56", "cash_balance": "$200.00"} + + def get_positions(self, _account): + return { + "items": [ + {"symbol": "SPY", "quantity": "2", "market_value": "21.00"}, + {"symbol": "AAPL", "quantity": "3", "market_value": "300.00"}, + ] + } + + adapters = build_runtime_broker_adapters( + client=AccountValueClient(), + account="12345678", + strategy_symbols=("SPY",), + ) + + portfolio = adapters.build_portfolio_port().get_portfolio_snapshot() + + assert portfolio.total_equity == 221.0 + assert [position.symbol for position in portfolio.positions] == ["SPY"] + assert portfolio.metadata["total_equity_source"] == "cash_plus_positions" + + def test_portfolio_snapshot_falls_back_to_cash_when_total_value_missing(): class CashOnlyClient(FakeClient): def get_balances(self, _account):