@@ -90,14 +90,19 @@ def _resolve_total_equity(
9090 buying_power : float | None ,
9191 position_market_value : float ,
9292 prefer_cash_plus_positions : bool = False ,
93+ cash_only_execution : bool = True ,
9394) -> tuple [float , str ]:
94- del buying_power # Cash-only: never use margin buying power for strategy equity.
95- if cash_balance is not None :
96- combined_value = float (cash_balance ) + max (0.0 , float (position_market_value ))
97- if prefer_cash_plus_positions :
98- return combined_value , "cash_plus_positions"
99- if combined_value > 0.0 :
100- return combined_value , "cash_plus_positions"
95+ if cash_only_execution :
96+ if cash_balance is not None :
97+ combined_value = float (cash_balance ) + max (0.0 , float (position_market_value ))
98+ if prefer_cash_plus_positions :
99+ return combined_value , "cash_plus_positions"
100+ if combined_value > 0.0 :
101+ return combined_value , "cash_plus_positions"
102+ elif buying_power is not None :
103+ combined_value = float (buying_power ) + max (0.0 , float (position_market_value ))
104+ if prefer_cash_plus_positions or combined_value > 0.0 :
105+ return combined_value , "buying_power_plus_positions"
101106
102107 balance_total = _first_numeric_by_keyword_groups (balances , _TOTAL_EQUITY_KEYWORD_GROUPS )
103108 if balance_total is not None :
@@ -120,6 +125,7 @@ class FirstradeBrokerAdapters:
120125 live_orders : bool = False
121126 live_order_ack : bool = False
122127 max_order_notional_usd : float | None = None
128+ cash_only_execution : bool = True
123129
124130 def normalize_symbol (self , symbol : str ) -> str :
125131 value = str (symbol or "" ).strip ().upper ()
@@ -247,26 +253,33 @@ def build_portfolio_snapshot(self) -> PortfolioSnapshot:
247253 )
248254 )
249255 cash_balance = _first_numeric_by_keyword_groups (balances , _CASH_BALANCE_KEYWORD_GROUPS )
250- buying_power = cash_balance
256+ reported_buying_power = _first_numeric_by_keyword_groups (balances , _BUYING_POWER_KEYWORD_GROUPS )
257+ buying_power = cash_balance if self .cash_only_execution else (
258+ reported_buying_power if reported_buying_power is not None else cash_balance
259+ )
251260 position_market_value = sum (position .market_value for position in positions )
252261 total_equity , total_equity_source = _resolve_total_equity (
253262 balances = balances ,
254263 cash_balance = cash_balance ,
255264 buying_power = buying_power ,
256265 position_market_value = position_market_value ,
257266 prefer_cash_plus_positions = bool (managed ),
267+ cash_only_execution = self .cash_only_execution ,
258268 )
259269 return PortfolioSnapshot (
260270 as_of = self .clock (),
261271 total_equity = float (total_equity ),
262- buying_power = float (cash_balance or 0.0 ),
272+ buying_power = float (buying_power or 0.0 ),
263273 cash_balance = cash_balance ,
264274 positions = tuple (positions ),
265275 metadata = {
266276 "broker" : "firstrade" ,
267277 "account_hash" : self .account_hash or mask_account_id (self .account ),
268278 "api_kind" : "unofficial-reverse-engineered" ,
269279 "total_equity_source" : total_equity_source ,
280+ "cash_only_execution" : self .cash_only_execution ,
281+ "market_currency_cash" : cash_balance ,
282+ "available_funds" : reported_buying_power ,
270283 },
271284 )
272285
@@ -321,6 +334,7 @@ def build_runtime_broker_adapters(
321334 live_orders : bool = False ,
322335 live_order_ack : bool = False ,
323336 max_order_notional_usd : float | None = None ,
337+ cash_only_execution : bool = True ,
324338) -> FirstradeBrokerAdapters :
325339 return FirstradeBrokerAdapters (
326340 client = client ,
@@ -331,4 +345,5 @@ def build_runtime_broker_adapters(
331345 live_orders = live_orders ,
332346 live_order_ack = live_order_ack ,
333347 max_order_notional_usd = max_order_notional_usd ,
348+ cash_only_execution = bool (cash_only_execution ),
334349 )
0 commit comments