Skip to content

Commit 9aec2b2

Browse files
committed
Add price action combined indicator live
1 parent cd507ed commit 9aec2b2

File tree

4 files changed

+145
-1
lines changed

4 files changed

+145
-1
lines changed

pyindicators/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
get_opening_gap_stats,
8080
strong_weak_high_low,
8181
strong_weak_high_low_signal,
82+
strong_weak_high_low_signal_live,
8283
get_strong_weak_high_low_stats,
8384
range_intelligence,
8485
range_intelligence_signal,
@@ -243,6 +244,7 @@ def get_version():
243244
'get_opening_gap_stats',
244245
'strong_weak_high_low',
245246
'strong_weak_high_low_signal',
247+
'strong_weak_high_low_signal_live',
246248
'get_strong_weak_high_low_stats',
247249
'range_intelligence',
248250
'range_intelligence_signal',

pyindicators/indicators/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
)
133133
from .strong_weak_high_low import (
134134
strong_weak_high_low, strong_weak_high_low_signal,
135-
get_strong_weak_high_low_stats
135+
strong_weak_high_low_signal_live, get_strong_weak_high_low_stats
136136
)
137137
from .range_intelligence import (
138138
range_intelligence, range_intelligence_signal,
@@ -282,6 +282,7 @@
282282
'get_opening_gap_stats',
283283
'strong_weak_high_low',
284284
'strong_weak_high_low_signal',
285+
'strong_weak_high_low_signal_live',
285286
'get_strong_weak_high_low_stats',
286287
'range_intelligence',
287288
'range_intelligence_signal',

pyindicators/indicators/strong_weak_high_low.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
192272
def 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
55.8 KB
Loading

0 commit comments

Comments
 (0)