Skip to content

Commit 14ff5ef

Browse files
committed
Add Pulse Mean Accelerator indicator and related image, and update liquidity sweeps indicator with pivot tracking structures. Also update README and __init__.py files to include the new indicator.
1 parent 28bcada commit 14ff5ef

9 files changed

Lines changed: 575 additions & 57 deletions

File tree

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,61 @@ pd_df.tail(10)
498498

499499
![SUPERTREND_CLUSTERING](https://github.com/coding-kitties/PyIndicators/blob/main/static/images/indicators/supertrend_clustering.png)
500500

501+
#### Pulse Mean Accelerator (PMA)
502+
503+
The Pulse Mean Accelerator is a trend-following overlay indicator
504+
translated from the Pine Script® by MisinkoMaster. It adds a
505+
volatility- and momentum-scaled acceleration offset to a base moving
506+
average. The acceleration accumulates over a configurable lookback:
507+
bars where source momentum exceeds MA momentum push the PMA further
508+
from the MA, while bars where the MA leads source momentum pull it
509+
back. Multiple MA types (RMA, SMA, EMA, WMA, DEMA, TEMA, HMA),
510+
volatility measures (ATR, Standard Deviation, MAD), and smoothing
511+
modes are supported.
512+
513+
**Parameters:**
514+
515+
| Parameter | Type | Default | Description |
516+
|---|---|---|---|
517+
| `source_column` | str | `"Close"` | Source price column |
518+
| `ma_type` | str | `"RMA"` | MA type: RMA, SMA, EMA, WMA, DEMA, TEMA, HMA |
519+
| `ma_length` | int | `20` | Lookback for the base moving average |
520+
| `accel_lookback` | int | `32` | Bars over which acceleration is accumulated |
521+
| `max_accel` | float | `0.2` | Maximum absolute acceleration factor |
522+
| `volatility_type` | str | `"Standard Deviation"` | Volatility: ATR, Standard Deviation, MAD |
523+
| `smooth_type` | str | `"Double Moving Average"` | Smoothing: NONE, Exponential, Extra Moving Average, Double Moving Average |
524+
| `use_confirmation` | bool | `True` | Require combined PMA+MA momentum to confirm trend flips |
525+
526+
**Output columns:** `pma`, `pma_ma`, `pma_trend`, `pma_long`, `pma_short`, `pma_acceleration`
527+
528+
```python
529+
import pandas as pd
530+
from pyindicators import (
531+
pulse_mean_accelerator,
532+
pulse_mean_accelerator_signal,
533+
get_pulse_mean_accelerator_stats,
534+
)
535+
536+
# --- With pandas ---
537+
df = pd.read_csv("data.csv")
538+
df = pulse_mean_accelerator(
539+
df,
540+
ma_type="RMA",
541+
ma_length=20,
542+
accel_lookback=32,
543+
max_accel=0.2,
544+
volatility_type="Standard Deviation",
545+
smooth_type="Double Moving Average",
546+
use_confirmation=True,
547+
)
548+
df = pulse_mean_accelerator_signal(df)
549+
stats = get_pulse_mean_accelerator_stats(df)
550+
print(stats)
551+
df[["Close", "pma", "pma_ma", "pma_trend", "pma_long", "pma_short"]].tail(10)
552+
```
553+
554+
![PULSE_MEAN_ACCELERATOR](https://github.com/coding-kitties/PyIndicators/blob/main/static/images/indicators/pulse_mean_accelerator.png)
555+
501556
### Momentum and Oscillators
502557

503558
Indicators that measure the strength and speed of price movements rather than the direction.
@@ -1983,6 +2038,8 @@ The function returns:
19832038
- Mitigation signals a change in market structure; the zone is no longer valid
19842039
- Increase `contact_count` for higher-quality, more reliable zones
19852040

2041+
![LIQUIDITY_POOLS](https://github.com/coding-kitties/PyIndicators/blob/main/static/images/indicators/liquidity_pools.png)
2042+
19862043
#### Liquidity Levels / Voids (VP)
19872044

19882045
Liquidity Levels / Voids is a Smart Money Concept indicator that uses volume-profile analysis between swing points to identify price levels where little volume was traded — these are *liquidity voids* that price tends to revisit.
@@ -2063,6 +2120,8 @@ The function returns:
20632120
- Use `liq_void_count` to gauge overall market imbalance
20642121
- Decrease `detection_length` for more frequent void detection on shorter timeframes
20652122

2123+
![LIQUIDITY_LEVELS_VOIDS](https://github.com/coding-kitties/PyIndicators/blob/main/static/images/indicators/liquidity_levels_voids.png)
2124+
20662125
### Pattern Recognition
20672126

20682127
#### Detect Peaks

pyindicators/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
get_pure_price_action_liquidity_sweep_stats,
3434
liquidity_pools, liquidity_pool_signal, get_liquidity_pool_stats,
3535
liquidity_levels_voids, liquidity_levels_voids_signal,
36-
get_liquidity_levels_voids_stats
36+
get_liquidity_levels_voids_stats,
37+
pulse_mean_accelerator, pulse_mean_accelerator_signal,
38+
get_pulse_mean_accelerator_stats
3739
)
3840
from .exceptions import PyIndicatorException
3941
from .date_range import DateRange
@@ -139,5 +141,8 @@ def get_version():
139141
'get_liquidity_pool_stats',
140142
'liquidity_levels_voids',
141143
'liquidity_levels_voids_signal',
142-
'get_liquidity_levels_voids_stats'
144+
'get_liquidity_levels_voids_stats',
145+
'pulse_mean_accelerator',
146+
'pulse_mean_accelerator_signal',
147+
'get_pulse_mean_accelerator_stats'
143148
]

pyindicators/indicators/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
liquidity_levels_voids, liquidity_levels_voids_signal,
6868
get_liquidity_levels_voids_stats
6969
)
70+
from .pulse_mean_accelerator import (
71+
pulse_mean_accelerator, pulse_mean_accelerator_signal,
72+
get_pulse_mean_accelerator_stats
73+
)
7074

7175
__all__ = [
7276
'sma',
@@ -154,5 +158,8 @@
154158
'get_liquidity_pool_stats',
155159
'liquidity_levels_voids',
156160
'liquidity_levels_voids_signal',
157-
'get_liquidity_levels_voids_stats'
161+
'get_liquidity_levels_voids_stats',
162+
'pulse_mean_accelerator',
163+
'pulse_mean_accelerator_signal',
164+
'get_pulse_mean_accelerator_stats'
158165
]

pyindicators/indicators/buyside_sellside_liquidity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def buyside_sellside_liquidity(
6767
Args:
6868
data: pandas or polars DataFrame with OHLC data.
6969
detection_length: Lookback period for pivot detection
70-
(default: 7). Pine equivalent: ``ta.pivothigh(liqLen, 1)``.
70+
(default: 7).
7171
margin: Divisor for the ATR margin band (default: 6.9).
7272
Effective margin = ``10 / margin * ATR(atr_period)``.
7373
buyside_margin: Multiplier for the breach zone around

pyindicators/indicators/liquidity_levels_voids.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
"""
22
Liquidity Levels / Voids (Volume Profile) Indicator
33
4-
Based on the Liquidity Levels/Voids (VP) [LuxAlgo] concept from
5-
TradingView. Uses volume-profile analysis between swing points to
4+
Uses volume-profile analysis between swing points to
65
identify price levels where little volume was traded — these are
76
*liquidity voids* that price tends to revisit.
87
@@ -86,8 +85,7 @@ def liquidity_levels_voids(
8685
Args:
8786
data: pandas or polars DataFrame with OHLCV data.
8887
detection_length: Lookback/look-ahead period for swing
89-
detection (default: 47). Equivalent to Pine Script's
90-
``ppLen`` parameter.
88+
detection (default: 47).
9189
threshold: Volume fraction below which a level is classified
9290
as a liquidity void (default: 0.21, i.e. 21 %). A level
9391
is a void if its volume < ``threshold * max_volume_level``.
@@ -156,7 +154,7 @@ def liquidity_levels_voids(
156154
active_voids: list[list[float]] = []
157155

158156
# Previous pivot bar index and previous HIGH/LOW values for
159-
# swing comparison (mirrors pp.x, pp.x1 in Pine).
157+
# swing comparison.
160158
prev_pivot_bar: int = -1
161159
prev_pivot_bar_2: int = -1
162160

@@ -225,7 +223,7 @@ def liquidity_levels_voids(
225223

226224
# Check if already filled in the
227225
# interval from vp_start to current
228-
# bar (mirrors Pine's fill loop)
226+
# bar
229227
filled_already = False
230228
mid = (lev_bot + lev_top) / 2.0
231229
fill_start = -1

pyindicators/indicators/liquidity_pools.py

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
"""
22
Liquidity Pools Indicator
33
4-
Faithful Python translation of the **Liquidity Pools [LuxAlgo]**
5-
indicator from TradingView (Pine Script v5, Oct 16 2024, 332 lines).
6-
74
Identifies liquidity pool zones by analysing high and low wicked
85
price areas, the number of contacts, and the frequency of visits.
96
10-
Algorithm overview (matching Pine Script exactly)
7+
Algorithm overview
118
-------------------------------------------------
129
1. Track a *highest* (``hst``) and *lowest* (``lst``) reference
1310
candle. On each bar, adjust these references when price makes
@@ -19,8 +16,8 @@
1916
body level must be stable (unchanged for 2 bars).
2017
2118
3. When enough contacts accumulate (``contact_count``) and the
22-
bars-since-last-wick counter crosses above ``confirmation_bars``
23-
(``ta.crossover(bs_hw, wait)`` in Pine), AND the close is on
19+
bars-since-last-wick counter crosses above ``confirmation_bars``,
20+
AND the close is on
2421
the correct side of the reference body level, a zone is created.
2522
2623
4. Zone creation uses a *running zone* pattern (``h_zn`` / ``l_zn``)
@@ -31,7 +28,7 @@
3128
- No overlap -> create a fresh running zone and push to array.
3229
3330
5. Zone extension: the most recent zone accumulates fractional volume
34-
from every overlapping candle (``get_civ`` in Pine).
31+
from every overlapping candle.
3532
3633
6. Zone mitigation: when price closes beyond the zone boundary for
3734
two consecutive bars, the zone is removed.
@@ -55,10 +52,6 @@
5552
def _get_civ(h: float, lo: float, v: float,
5653
zone_top: float, zone_bot: float) -> float:
5754
"""Return the fraction of candle volume that lies inside *zone*.
58-
59-
Mirrors Pine Script's ``get_civ`` helper:
60-
``nz((_h - _l) / _r, 1) * volume`` where _h/_l are clamped to
61-
the zone boundaries.
6255
"""
6356
r = h - lo
6457
if r <= 0:
@@ -94,17 +87,11 @@ def liquidity_pools(
9487
) -> Union[PdDataFrame, PlDataFrame]:
9588
"""Detect Liquidity Pool zones on OHLC(V) data.
9689
97-
Faithful translation of:
98-
*Liquidity Pools [LuxAlgo]* -- Pine Script v5, Oct 16 2024.
99-
10090
Args:
10191
data: pandas or polars DataFrame with OHLC(V) data.
10292
contact_count: Minimum wick contacts required to form a zone
103-
(Pine: ``cNum``, default 2).
10493
gap_bars: Minimum bars between successive contacts
105-
(Pine: ``gapCount``, default 5).
10694
confirmation_bars: Bars to wait before confirming a zone
107-
(Pine: ``wait``, default 10).
10895
high_column: Column name for highs.
10996
low_column: Column name for lows.
11097
open_column: Column name for opens.
@@ -188,7 +175,6 @@ def liquidity_pools(
188175
return pl.from_pandas(df)
189176
return df
190177

191-
# -- Pine: var declarations (persistent across bars) --------------
192178
c_top_0 = max(opens[0], closes[0])
193179
c_bot_0 = min(opens[0], closes[0])
194180

@@ -210,7 +196,6 @@ def liquidity_pools(
210196
hi_vol = 0.0 # var float hi_vol = 0
211197
lo_vol = 0.0 # var float lo_vol = 0
212198

213-
# Running zones (Pine: var zn h_zn, l_zn -- initially na)
214199
# Each: {top, bottom, state, vol, start_bar, right_bar} or None
215200
h_zn = None
216201
l_zn = None
@@ -219,11 +204,10 @@ def liquidity_pools(
219204
bull_zones: list[dict] = []
220205
bear_zones: list[dict] = []
221206

222-
# Pine: var int bs_hw = 0, bs_lw = 0
207+
# var int bs_hw = 0, bs_lw = 0
223208
bs_hw = 0
224209
bs_lw = 0
225210

226-
# Previous-bar references for Pine [1] / [2] lookbacks
227211
prev_h_wick = False
228212
prev_l_wick = False
229213
hst_t_1ago = hst["t"]
@@ -244,8 +228,6 @@ def liquidity_pools(
244228
c_bot = min(o, c)
245229

246230
# -- 1. Adjusting High and Low Check Boundaries ---------------
247-
# Pine: if (high > hst.h) and ((c_top > hst.h) or
248-
# (c_top < hst.t))
249231
if h > hst["h"] and (c_top > hst["h"] or c_top < hst["t"]):
250232
if h_count > 1:
251233
# Reset low reference to current candle
@@ -264,8 +246,6 @@ def liquidity_pools(
264246
h_count = 1
265247
last_h_wick = bar
266248

267-
# Pine: if (low < lst.l) and ((c_bot < lst.l) or
268-
# (c_bot > lst.b))
269249
if lo < lst["l"] and (c_bot < lst["l"] or c_bot > lst["b"]):
270250
if l_count > 1:
271251
# Reset high reference to current candle
@@ -285,43 +265,34 @@ def liquidity_pools(
285265
last_l_wick = bar
286266

287267
# -- 2. Compute wicks for current bar -------------------------
288-
# Pine: h_wick = high > hst.t and c_top <= hst.t
289268
h_wick = (h > hst["t"]) and (c_top <= hst["t"])
290-
# Pine: l_wick = low < lst.b and c_bot >= lst.b
291269
l_wick = (lo < lst["b"]) and (c_bot >= lst["b"])
292270

293-
# -- 3. Counting contacts (Pine uses [1] / [2] refs) ---------
294-
# Pine: if (h_wick[1] and (hst.t[1] == hst.t[2])
295-
# and (bs_hw > gapCount))
271+
# -- 3. Counting contacts ---------
296272
if (prev_h_wick
297273
and hst_t_1ago == hst_t_2ago
298274
and prev_bs_hw > gap_bars):
299275
h_count += 1
300276
last_h_wick = bar - 1
301277

302-
# Pine: if (l_wick[1] and (lst.b[1] == lst.b[2])
303-
# and (bs_lw > gapCount))
304278
if (prev_l_wick
305279
and lst_b_1ago == lst_b_2ago
306280
and prev_bs_lw > gap_bars):
307281
l_count += 1
308282
last_l_wick = bar - 1
309283

310284
# -- 4. Bars since last wick ----------------------------------
311-
# Pine: bs_hw := math.abs(last_h_wick - bar_index)
312285
bs_hw = abs(last_h_wick - bar)
313286
bs_lw = abs(last_l_wick - bar)
314287

315288
# -- 5. Track outer extremes ----------------------------------
316-
# Pine: if (high > hst.h) hst.h := high
317289
if h > hst["h"]:
318290
hst["h"] = h
319-
# Pine: if (low < lst.l) lst.l := low
291+
320292
if lo < lst["l"]:
321293
lst["l"] = lo
322294

323295
# -- 6. Volume tracking (reference-level volume) --------------
324-
# Pine: hst_vol = max((high - hst.t), 0) / (high-low) * vol
325296
h_range = h - lo
326297
if h_range > 0:
327298
hi_vol += max(h - hst["t"], 0) / h_range * v
@@ -338,8 +309,6 @@ def liquidity_pools(
338309
)
339310

340311
# -- 7a. Bearish pool (resistance) from high contacts ---------
341-
# Pine: if (h_count >= cNum) and ta.crossover(bs_hw, wait)
342-
# and (close < hst.t)
343312
if (h_count >= contact_count
344313
and hw_cross
345314
and c < hst["t"]):
@@ -394,8 +363,6 @@ def liquidity_pools(
394363
bear_zones.append(h_zn)
395364

396365
# -- 7b. Bullish pool (support) from low contacts -------------
397-
# Pine: if (l_count >= cNum) and ta.crossover(bs_lw, wait)
398-
# and (close > lst.b)
399366
if (l_count >= contact_count
400367
and lw_cross
401368
and c > lst["b"]):
@@ -460,7 +427,7 @@ def liquidity_pools(
460427
zb = z["bottom"]
461428

462429
# Most recent zone: accumulate candle volume if
463-
# overlapping (Pine get_civ) and extend line
430+
# overlapping and extend line
464431
if i == len(bull_zones) - 1:
465432
if c > zt:
466433
z["right_bar"] = bar
@@ -488,7 +455,7 @@ def liquidity_pools(
488455
zb = z["bottom"]
489456

490457
# Most recent zone: accumulate candle volume if
491-
# overlapping (Pine get_civ) and extend line
458+
# overlapping and extend line
492459
if i == len(bear_zones) - 1:
493460
if c < zb:
494461
z["right_bar"] = bar

pyindicators/indicators/liquidity_sweeps.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ def liquidity_sweeps(
4949
A liquidity sweep occurs when price momentarily pierces a swing
5050
high or swing low—grabbing resting liquidity—before reversing.
5151
52-
Three detection modes mirror the Pine Script original:
53-
5452
* ``"wicks"`` – only wick-through sweeps (high > swing high but
5553
close < swing high, or low < swing low but close > swing low).
5654
* ``"outbreak_retest"`` – only outbreak-and-retest sweeps (price
@@ -246,8 +244,7 @@ def _detect_pivot_highs(high: np.ndarray, length: int) -> np.ndarray:
246244
Detect pivot highs. A pivot high at index *i* means
247245
``high[i]`` >= all highs in ``[i-length … i+length]``.
248246
249-
The result is delayed by *length* bars (like Pine's
250-
``ta.pivothigh(len, len)``).
247+
The result is delayed by *length* bars
251248
"""
252249
n = len(high)
253250
pivots = np.full(n, np.nan)
@@ -302,7 +299,7 @@ def _liquidity_sweeps_pandas(
302299
pivot_lows = _detect_pivot_lows(low, swing_length)
303300

304301
# ------------------------------------------------------------------ #
305-
# Pivot tracking structures (mirrors Pine Script UDT arrays) #
302+
# Pivot tracking structures #
306303
# ------------------------------------------------------------------ #
307304
# Each tracked pivot: {price, bar_idx, broken, mitigated, wick_used}
308305
tracked_highs: List[Dict] = []

0 commit comments

Comments
 (0)