@@ -248,6 +248,7 @@ class ExecutionCycleResult:
248248 note_logs : tuple [str , ...]
249249 action_done : bool
250250 dry_run_orders : tuple [dict , ...] = ()
251+ quote_snapshots : tuple [dict , ...] = ()
251252
252253
253254DEFAULT_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD = 1000.0
@@ -265,6 +266,17 @@ def _coerce_order_quantity(value):
265266 return value
266267
267268
269+ def _serialize_quote_snapshot (snapshot ) -> dict :
270+ return {
271+ "symbol" : str (getattr (snapshot , "symbol" , "" ) or "" ).strip ().upper (),
272+ "as_of" : str (getattr (snapshot , "as_of" , "" ) or "" ),
273+ "last_price" : float (getattr (snapshot , "last_price" , 0.0 ) or 0.0 ),
274+ "bid_price" : getattr (snapshot , "bid_price" , None ),
275+ "ask_price" : getattr (snapshot , "ask_price" , None ),
276+ "currency" : str (getattr (snapshot , "currency" , "" ) or "" ).strip (),
277+ }
278+
279+
268280def _safe_haven_cash_symbols (* , portfolio : dict , allocation : dict ) -> tuple [str , ...]:
269281 symbols : list [str ] = []
270282 for symbol in allocation .get ("safe_haven_symbols" , ()):
@@ -422,9 +434,12 @@ def _sell_order_quantity(
422434 )
423435
424436
425- def safe_quote_last_price (symbol , * , market_data_port , notify_issue ):
437+ def safe_quote_last_price (symbol , * , market_data_port , notify_issue , quote_recorder = None ):
426438 try :
427- return float (market_data_port .get_quote (symbol ).last_price )
439+ snapshot = market_data_port .get_quote (symbol )
440+ if quote_recorder is not None :
441+ quote_recorder (snapshot )
442+ return float (snapshot .last_price )
428443 except Exception as exc :
429444 notify_issue ("Quote failed" , f"Symbol: { symbol } \n { exc } " )
430445 return None
@@ -574,6 +589,7 @@ def execute_rebalance_cycle(
574589 skip_logs : list [str ] = []
575590 note_logs : list [str ] = []
576591 dry_run_orders : list [dict ] = []
592+ quote_snapshots_by_symbol : dict [str , dict ] = {}
577593 small_account_cash_note_keys : set [str ] = set ()
578594 action_done = False
579595 sell_submitted = False
@@ -585,6 +601,12 @@ def execute_rebalance_cycle(
585601 def market_symbol (symbol ):
586602 return _market_symbol (symbol , symbol_suffix = symbol_suffix )
587603
604+ def record_quote_snapshot (snapshot ) -> None :
605+ payload = _serialize_quote_snapshot (snapshot )
606+ symbol = payload .get ("symbol" )
607+ if symbol :
608+ quote_snapshots_by_symbol [symbol ] = payload
609+
588610 strategy_assets = tuple (allocation ["strategy_symbols" ])
589611 market_values = dict (portfolio ["market_values" ])
590612 quantities = dict (portfolio ["quantities" ])
@@ -718,6 +740,7 @@ def record_dry_run(symbol, side, quantity, price, *, order_type):
718740 market_symbol (symbol ),
719741 market_data_port = market_data_port ,
720742 notify_issue = notify_issue ,
743+ quote_recorder = record_quote_snapshot ,
721744 )
722745 if price is None :
723746 continue
@@ -807,6 +830,7 @@ def record_dry_run(symbol, side, quantity, price, *, order_type):
807830 market_symbol (cash_sweep_symbol ),
808831 market_data_port = market_data_port ,
809832 notify_issue = notify_issue ,
833+ quote_recorder = record_quote_snapshot ,
810834 )
811835 if sweep_price is not None and sweep_price > 0.0 :
812836 funding_needs = []
@@ -815,6 +839,7 @@ def record_dry_run(symbol, side, quantity, price, *, order_type):
815839 market_symbol (buy_symbol ),
816840 market_data_port = market_data_port ,
817841 notify_issue = notify_issue ,
842+ quote_recorder = record_quote_snapshot ,
818843 )
819844 if buy_price is None :
820845 continue
@@ -958,6 +983,7 @@ def record_dry_run(symbol, side, quantity, price, *, order_type):
958983 market_symbol (symbol ),
959984 market_data_port = market_data_port ,
960985 notify_issue = notify_issue ,
986+ quote_recorder = record_quote_snapshot ,
961987 )
962988 if price is None :
963989 continue
@@ -1078,6 +1104,7 @@ def record_dry_run(symbol, side, quantity, price, *, order_type):
10781104 market_symbol (cash_sweep_symbol ),
10791105 market_data_port = market_data_port ,
10801106 notify_issue = notify_issue ,
1107+ quote_recorder = record_quote_snapshot ,
10811108 )
10821109 if cash_sweep_price is not None and cash_sweep_price > 0.0 and investable_cash > cash_sweep_price * 2 :
10831110 substitution_threshold = max (
@@ -1132,4 +1159,5 @@ def record_dry_run(symbol, side, quantity, price, *, order_type):
11321159 note_logs = tuple (note_logs ),
11331160 action_done = action_done ,
11341161 dry_run_orders = tuple (dry_run_orders ),
1162+ quote_snapshots = tuple (quote_snapshots_by_symbol .values ()),
11351163 )
0 commit comments