@@ -189,6 +189,86 @@ def strong_weak_high_low_signal(
189189 )
190190
191191
192+ def strong_weak_high_low_signal_live (
193+ data : Union [PdDataFrame , PlDataFrame ],
194+ swing_lookback : int = 50 ,
195+ sw_high_column : str = "sw_high" ,
196+ sw_low_column : str = "sw_low" ,
197+ sw_high_type_column : str = "sw_high_type" ,
198+ sw_low_type_column : str = "sw_low_type" ,
199+ live_signal_column : str = "sw_live_signal" ,
200+ live_high_column : str = "sw_live_high" ,
201+ live_low_column : str = "sw_live_low" ,
202+ live_high_type_column : str = "sw_live_high_type" ,
203+ live_low_type_column : str = "sw_live_low_type" ,
204+ ) -> Union [PdDataFrame , PlDataFrame ]:
205+ """
206+ Generate live trading signals from Strong/Weak High-Low (no lookahead).
207+
208+ The standard ``strong_weak_high_low()`` indicator uses pivot detection
209+ that looks forward by ``pivot_length = swing_lookback // 5`` bars,
210+ causing lookahead bias unsuitable for live trading or realistic
211+ backtesting.
212+
213+ This function delays the signal by the pivot length to eliminate
214+ lookahead. A swing point detected at bar *i* is only signaled at
215+ bar *i + pivot_length*, when it would actually be confirmed in
216+ real-time.
217+
218+ Signal values:
219+ - 1 : Strong Low confirmed (bullish — institution buying)
220+ - -1 : Strong High confirmed (bearish — institution selling)
221+ - 0 : Weak swing, no swing, or not yet confirmed
222+
223+ Args:
224+ data: DataFrame with SW columns already computed via
225+ ``strong_weak_high_low()``.
226+ swing_lookback: Must match the value used in the base
227+ indicator call (default: 50). Used to calculate delay.
228+ sw_high_column: Column with swing high flags.
229+ sw_low_column: Column with swing low flags.
230+ sw_high_type_column: Column with high type classification.
231+ sw_low_type_column: Column with low type classification.
232+ live_signal_column: Output — delayed combined signal.
233+ live_high_column: Output — delayed swing high flag.
234+ live_low_column: Output — delayed swing low flag.
235+ live_high_type_column: Output — delayed high type.
236+ live_low_type_column: Output — delayed low type.
237+
238+ Returns:
239+ DataFrame with added live signal columns.
240+
241+ Example:
242+ >>> df = strong_weak_high_low(df, swing_lookback=50)
243+ >>> df = strong_weak_high_low_signal_live(df, swing_lookback=50)
244+ >>> # Use df["sw_live_signal"] for trading
245+ """
246+ if isinstance (data , PlDataFrame ):
247+ pdf = data .to_pandas ()
248+ pdf = _compute_live_signal (
249+ pdf , swing_lookback ,
250+ sw_high_column , sw_low_column ,
251+ sw_high_type_column , sw_low_type_column ,
252+ live_signal_column , live_high_column , live_low_column ,
253+ live_high_type_column , live_low_type_column ,
254+ )
255+ import polars as pl
256+ return pl .from_pandas (pdf )
257+
258+ if isinstance (data , PdDataFrame ):
259+ return _compute_live_signal (
260+ data , swing_lookback ,
261+ sw_high_column , sw_low_column ,
262+ sw_high_type_column , sw_low_type_column ,
263+ live_signal_column , live_high_column , live_low_column ,
264+ live_high_type_column , live_low_type_column ,
265+ )
266+
267+ raise PyIndicatorException (
268+ "Input data must be a pandas or polars DataFrame."
269+ )
270+
271+
192272def get_strong_weak_high_low_stats (
193273 data : Union [PdDataFrame , PlDataFrame ],
194274 sw_high_column : str = "sw_high" ,
@@ -425,3 +505,64 @@ def _compute_signal(
425505
426506 df [sig_col ] = sig
427507 return df
508+
509+
510+ def _compute_live_signal (
511+ df : PdDataFrame ,
512+ swing_lookback : int ,
513+ sh_col : str , sl_col : str ,
514+ sh_type : str , sl_type : str ,
515+ live_sig_col : str ,
516+ live_high_col : str , live_low_col : str ,
517+ live_high_type_col : str , live_low_type_col : str ,
518+ ) -> PdDataFrame :
519+ """
520+ Generate delayed (live-safe) signals by shifting by pivot_length.
521+
522+ The pivot detection looks forward by piv_len = swing_lookback // 5
523+ bars. To eliminate lookahead, we delay all swing outputs by this
524+ amount — a swing detected at bar i is output at bar i + piv_len.
525+ """
526+ n = len (df )
527+ piv_len = max (swing_lookback // 5 , 2 )
528+
529+ sh_flags = df [sh_col ].values
530+ sl_flags = df [sl_col ].values
531+ sh_types = df [sh_type ].values
532+ sl_types = df [sl_type ].values
533+
534+ # Delayed output arrays
535+ live_sig = np .zeros (n , dtype = int )
536+ live_high = np .zeros (n , dtype = int )
537+ live_low = np .zeros (n , dtype = int )
538+ live_h_type = np .full (n , None , dtype = object )
539+ live_l_type = np .full (n , None , dtype = object )
540+
541+ for i in range (n ):
542+ # Delayed index: swing at i becomes visible at i + piv_len
543+ out_idx = i + piv_len
544+ if out_idx >= n :
545+ continue
546+
547+ # Swing high → delay
548+ if sh_flags [i ] == 1 :
549+ live_high [out_idx ] = 1
550+ live_h_type [out_idx ] = sh_types [i ]
551+ if sh_types [i ] == "Strong" :
552+ live_sig [out_idx ] = - 1
553+
554+ # Swing low → delay
555+ if sl_flags [i ] == 1 :
556+ live_low [out_idx ] = 1
557+ live_l_type [out_idx ] = sl_types [i ]
558+ if sl_types [i ] == "Strong" :
559+ # If both strong high and low on same delayed bar,
560+ # low (bullish) takes precedence
561+ live_sig [out_idx ] = 1
562+
563+ df [live_sig_col ] = live_sig
564+ df [live_high_col ] = live_high
565+ df [live_low_col ] = live_low
566+ df [live_high_type_col ] = live_h_type
567+ df [live_low_type_col ] = live_l_type
568+ return df
0 commit comments