@@ -115,6 +115,7 @@ def list_signals(
115115 expires_to : Optional [Any ] = None ,
116116 holding_only : bool = False ,
117117 account_id : Optional [int ] = None ,
118+ stock_identities : Optional [List [Tuple [str , str ]]] = None ,
118119 page : int = 1 ,
119120 page_size : int = 20 ,
120121 ) -> Dict [str , Any ]:
@@ -133,9 +134,27 @@ def list_signals(
133134 expires_from_dt = self ._parse_datetime (expires_from )
134135 expires_to_dt = self ._parse_datetime (expires_to )
135136 stock_codes = self ._stock_filter_codes (stock_code , market = market_norm )
136- stock_identities = None
137-
138- if holding_only :
137+ stock_identity_filters : Optional [List [Tuple [str , str ]]] = None
138+
139+ if stock_identities is not None :
140+ # Explicit identities come from a caller-owned snapshot; skip cached holdings entirely.
141+ requested_codes = set (stock_codes or [])
142+ normalized_identities : set [Tuple [str , str ]] = set ()
143+ for identity_market , identity_code in stock_identities :
144+ if not str (identity_code or "" ).strip ():
145+ continue
146+ identity_market_norm = self ._normalize_market (identity_market )
147+ if market_norm and identity_market_norm != market_norm :
148+ continue
149+ identity_code_norm = self ._normalize_stock_code (identity_code , market = identity_market_norm )
150+ if requested_codes and identity_code_norm not in requested_codes :
151+ continue
152+ normalized_identities .add ((identity_market_norm , identity_code_norm ))
153+ stock_identity_filters = sorted (normalized_identities )
154+ stock_codes = None
155+ if not stock_identity_filters :
156+ return {"items" : [], "total" : 0 , "page" : safe_page , "page_size" : safe_page_size }
157+ elif holding_only :
139158 held_identities = self ._cached_holding_identities (account_id = account_id )
140159 if market_norm :
141160 held_identities = {
@@ -146,14 +165,14 @@ def list_signals(
146165 held_identities = {
147166 identity for identity in held_identities if identity [1 ] in requested_codes
148167 }
149- stock_identities = sorted (held_identities )
168+ stock_identity_filters = sorted (held_identities )
150169 stock_codes = None
151- if not stock_identities :
170+ if not stock_identity_filters :
152171 return {"items" : [], "total" : 0 , "page" : safe_page , "page_size" : safe_page_size }
153172
154173 rows , total = self .repo .list (
155174 stock_codes = stock_codes ,
156- stock_identities = stock_identities ,
175+ stock_identities = stock_identity_filters ,
157176 market = market_norm ,
158177 action = action_norm ,
159178 market_phase = market_phase_norm ,
@@ -183,12 +202,13 @@ def list_signals(
183202 created_to = created_to_dt ,
184203 expires_from = expires_from_dt ,
185204 expires_to = expires_to_dt ,
205+ stock_identities = stock_identity_filters ,
186206 holding_only = holding_only ,
187207 ):
188208 self ._backfill_analysis_signal_from_history (source_report_id_norm )
189209 rows , total = self .repo .list (
190210 stock_codes = stock_codes ,
191- stock_identities = stock_identities ,
211+ stock_identities = stock_identity_filters ,
192212 market = market_norm ,
193213 action = action_norm ,
194214 market_phase = market_phase_norm ,
@@ -276,6 +296,7 @@ def _should_backfill_history_bound_analysis_signal(
276296 created_to : Optional [datetime ],
277297 expires_from : Optional [datetime ],
278298 expires_to : Optional [datetime ],
299+ stock_identities : Optional [List [Tuple [str , str ]]],
279300 holding_only : bool ,
280301 ) -> bool :
281302 """Only lazy-backfill for the exact report section query used by Web."""
@@ -296,6 +317,7 @@ def _should_backfill_history_bound_analysis_signal(
296317 created_to ,
297318 expires_from ,
298319 expires_to ,
320+ stock_identities ,
299321 holding_only ,
300322 )
301323 )
0 commit comments