Skip to main content

Yet Another Library for technical indicators, but this time with more stationarity.

Project description

indicatorPy

Yet another technical-indicator library — but this one is built around stationarity. Most indicators are normalized (typically through the normal CDF) into a bounded, zero-centred range, so their output is comparable across instruments and regimes and is ready to use as a machine-learning feature.

pip install indicatorPy

Quick start

import numpy as np
from indicators.trend import RSI, ADAPTIVE_BANDPASS
from indicators.volume import MONEY_FLOW

# bring your own OHLCV as numpy arrays / lists
open_, high, low, close, volume = load_my_data()

rsi = RSI().calculate(14, close)                       # centered RSI, ~[-50, +50]
mf  = MONEY_FLOW().calculate(20, high, low, close, volume)

bp   = ADAPTIVE_BANDPASS()
sig  = bp.calculate(0.3, 3, close)                     # Ehlers adaptive bandpass
lead = bp.lead_signal                                  # extra "lead" series it exposes

Conventions (read once, applies to all)

  • Construct, then call: every class indicator is used as IND().calculate(...). A handful are plain functions (cmma, entropy, rangeInterQuartileRangeRatio).
  • Output shape: calculate returns a numpy array the same length as the input (one value per bar). Plain functions noted below return a scalar or a pandas Series.
  • Stationarity / range: most indicators pass through the standard normal CDF and land in a bounded, mean-centred range — usually [-50, +50] (a few are [-1, +1], [0, 1], or 0–100). The bound is noted per indicator.
  • Warm-up: the first few bars (until the lookback/filter is satisfied) are filled with 0 (a few use NaN — noted explicitly).
  • Integer params: numeric lengths are rounded internally via int(x + 0.5), so floats are accepted.
  • Variant selector: some indicators take a string first argument (var_num) that chooses which output to return (e.g. AROON "up"/"down"/"diff").
  • Extra attributes: ADAPTIVE_BANDPASS exposes .lead_signal; several filters also store their output on .result.

Indicators at a glance

Trend, momentum & normalized oscillators · RSI · DETRENDED_RSI · STOCHASTIC · STOCHASTIC_RSI · MA_DIFFERENCE · PRICE_INT · PPO · MACD · POLY_TREND · ADX · AROON · FTI

Ehlers cycle & adaptive indicators · ROOFING_FILTER · ADAPTIVE_RSI · ADAPTIVE_STOCHASTIC · ADAPTIVE_BANDPASS · EVEN_BETTER_SINEWAVE · INVERSE_FISHER_STOCHASTIC · DECYCLER_OSCILLATOR

Deviation & expectation · CLOSE_MINUS_MOVING_AVERAGE · POLY_DEVIATION · PRICE_CHANGE_OSCILLATOR · VARIANCE_RATIO

Information content · entropy · rangeInterQuartileRangeRatio · ENTROPY · MUTUAL_INFORMATION

Volume · VSA · INTRADAY_INTENSITY · MONEY_FLOW · REACTIVITY · PRICE_VOLUME_FIT · VOLUME_WEIGHTED_MA_RATIO · ON_BALANCE_VOLUME · VOLUME_INDEX · VOLUME_MOMENTUM

Multi-market · JANUSUtilities · CommonMethods


Trend, momentum & normalized oscillators

from indicators.trend import ...

RSI

Relative Strength Index, centred (raw RSI − 50) so it is stationary.

RSI().calculate(period, close)          # e.g. RSI().calculate(14, close)

period — RSI lookback (Wilder smoothing). In: close · Out: array, ≈[-50, +50]; first min(period, N) bars are 0. (When period == 2 a log transform is applied for near-normality.)

RSI measures momentum by comparing average upward price changes to total average movement (up and down). The algorithm applies Wilder's exponential smoothing to positive and negative price deltas separately, then outputs 100 * upsum / (upsum + dnsum) - 50, centering the classic 0–100 RSI scale at 0 for stationarity. This places traditional overbought territory (RSI > 70) at centered value +20 and oversold (RSI < 30) at −20, with the zero-crossing at the neutral RSI point. For period == 2 only, a logarithmic transform is applied to approximate a normal distribution. Typical periods are 14 or higher; the centered variant prioritizes statistical stationarity over the traditional 70/30 thresholds.

DETRENDED_RSI

A short-period RSI regressed against a long-period RSI; the residual isolates short-term momentum from the longer trend.

DETRENDED_RSI().calculate(short_len, long_len, reg_len, close)   # e.g. (9, 25, 20, close)

short_len — short RSI length · long_len — long/detrender RSI length (must be > short) · reg_len — regression lookback. In: close · Out: array; first long_len + reg_len − 1 bars are 0.

Detrended RSI removes the trend component from short-period momentum by computing a short-period RSI and a longer-period RSI (the detrender), then linearly regressing the short RSI against the long RSI over a reg_len-bar window and outputting the residual: (short_RSI − mean_short) − coef · (long_RSI − mean_long), where coef is the regression slope. The result is a zero-mean oscillator: positive values mean momentum is stronger than the trend predicts, negative means weaker. When short_len == 2, the short RSI is log-transformed before regression. Requires long_len > short_len.

STOCHASTIC

Position of the close within the high–low range, scaled and centred to [-50, +50].

STOCHASTIC().calculate(lookback, smoothing, high, low, close)    # e.g. (14, 1, high, low, close)

lookback — range window · smoothing0 raw, 1 %K, 2 %D (1/3–2/3 EMA passes). In: high, low, close · Out: array, [-50, +50]; first lookback − 1 bars are 0.

The Stochastic oscillator measures where the close sits within the high–low range over the lookback, answering whether price is near overbought or oversold levels. It computes the raw stochastic (close − min_low) / (max_high − min_low), then applies exponential smoothing controlled by smoothing: 0 uses raw values, 1 applies one smooth (the %K line, with 1/3–2/3 coefficients), 2 applies a second smooth (%D). The output is scaled and centred to [-50, +50], where +50 is overbought (close at the high), −50 is oversold (close at the low), and 0 is neutral. Use smoothing=1 for a standard %K or 2 for the smoother %D commonly used in strategies.

STOCHASTIC_RSI

A stochastic applied to the RSI, with optional EMA smoothing.

STOCHASTIC_RSI().calculate(rsi_len, stoch_len, ema_len, close)   # e.g. (14, 14, 3, close)

rsi_len — RSI period · stoch_len — stochastic window · ema_len — EMA smoothing (≤ 1 = none). In: close · Out: array, [-50, +50]; first rsi_len + stoch_len − 1 bars are 0.

Stochastic RSI measures where the current RSI ranks within its own recent range — i.e. whether momentum itself is at an extreme. It first computes RSI (Wilder smoothing) over rsi_len, then applies the stochastic formula 100 * (RSI − min_RSI) / (max_RSI − min_RSI) - 50 over a stoch_len-bar window of prior RSI values. The result spans [-50, +50]: positive = RSI near its recent highs (potential overbought), negative = near recent lows (oversold), 0 = midpoint. Optional EMA smoothing of ema_len periods can be applied (≤ 1 disables it). A tiny epsilon (1e-60) guards the denominator when the RSI range is flat.

MA_DIFFERENCE

ATR-normalized difference between a short and a (lagged) long moving average, CDF-mapped.

MA_DIFFERENCE().calculate(short, long, lag, open_, high, low, close)   # e.g. (10, 20, 0, o, h, l, c)

short, long — MA periods · lag — lag applied to the long MA. In: OHLC · Out: array, [-50, +50]; first min(long + lag, N) bars are 0.

MA_DIFFERENCE compares a short- and long-term moving average, normalized for volatility into a stationary oscillator. It computes 100 * norm.cdf(1.5 * normalized_diff) - 50, where normalized_diff = (short_ma − long_ma) / (sqrt(|lag_adjustment|) · ATR) and lag_adjustment = 0.5·(long − short) + lag (accounting for the temporal offset between the two MAs). The output oscillates in [-50, +50] centered at 0: above 0 the short MA leads the long (bullish), below 0 it lags (bearish). ATR normalization makes it scale-invariant across volatility regimes. Typical settings: short 5–13, long 20–50, lag 1–10.

PRICE_INT

Price intensity: candle body (close − open) relative to the true range, optionally EMA-smoothed, then CDF-normalized.

PRICE_INT().calculate(smooth, open_, high, low, close)    # e.g. (20, o, h, l, c)

smooth — EMA smoothing period (1 = none). In: OHLC · Out: array, ≈[-50, +50].

PRICE_INT measures directional intrabar intensity as close-minus-open over a true-range-like denominator. The first bar uses high − low; later bars use the max of high − low, high − prior_close, and prior_close − low. The raw ratio is optionally EMA-smoothed (alpha = 2/(n+1), where n = round(smooth)), then mapped through the normal CDF as 100 * norm.cdf(0.8 · sqrt(n) · x) - 50, giving a bounded (-50, +50) output centered at 0. Positive = bullish bars (close above open), negative = bearish, with magnitude reflecting directional strength relative to range. Larger smooth both smooths and compresses extremes (the sqrt(n) factor). The denominator has a 1e-60 floor against division by zero.

PPO

Percentage Price Oscillator, 100·(shortEMA − longEMA)/longEMA, CDF-normalized; when smooth > 1 it returns the normalized PPO minus its own smoothed version.

PPO().calculate(short, long, smooth, close)    # e.g. (12, 26, 9, close)

short, long — EMA lengths · smooth — smoothing of the PPO line. In: close · Out: array, ≈[-50, +50].

The Percentage Price Oscillator compares a short- and long-term EMA of price as a percentage: 100 * (shortEMA − longEMA) / longEMA. The implementation normalizes that raw PPO through the normal CDF as 100 * cdf(0.2 · raw_PPO) - 50, bounding values to [-50, +50] and taming outliers. If smooth > 1, the output becomes the normalized PPO minus its own exponential smoothing — i.e. the deviation from its trend. Positive values indicate bullish momentum (short-term strength), negative bearish; zero-crossings flag trend changes, and because the range is normalized, actionable signals come from directional shifts rather than fixed thresholds.

MACD

EMA difference normalized by ATR, CDF-mapped to ≈[-50, +50]; when smooth > 1 the smoothed value is subtracted back out.

MACD().calculate(short, long, smooth, open_, high, low, close)    # e.g. (12, 26, 9, o, h, l, c)

short, long — EMA lengths · smooth — second smoothing pass. In: OHLC (open_ is accepted but unused) · Out: array, ≈[-50, +50]; first value is 0.

MACD measures momentum and trend direction from the gap between a short- and long-term EMA of price. Two EMAs are formed with decay factors 2/(short+1) and 2/(long+1); their difference is normalized by sqrt(|0.5·(long − short)|) · ATR (volatility scaling) and mapped through the normal CDF as 100·Φ(x) − 50, which bounds the output to [-50, +50] centered at 0 (equal EMAs → Φ(0)=0.50). When smooth > 1, that output is exponentially smoothed and subtracted from itself, producing a tighter MACD-histogram-style oscillator around zero. Positive = bullish (short-term strength); negative = bearish.

POLY_TREND

Polynomial (Legendre) trend on log price, normalized by ATR and goodness-of-fit, CDF-mapped.

POLY_TREND().calculate(degree, fit_len, atr_len, open_, high, low, close)   # e.g. (1, 20, 14, o, h, l, c)

degree1 linear / 2 quadratic / 3 cubic (numeric) · fit_len — trend window · atr_len — ATR window. In: OHLC · Out: array, ≈[-50, +50]; first max(fit_len − 1, atr_len) bars are 0.

POLY_TREND fits an orthogonal Legendre polynomial (linear, quadratic, or cubic) to log-prices over the window and reports trend strength as a probability-scaled statistic. The trend coefficient is the dot product of the log-price window with the chosen Legendre basis, normalized by log-scale ATR times k (k = fit_len − 1, except k = 2 when fit_len == 2) and weighted by the fit's R². It is then scaled by 2.0 and mapped through the normal CDF as 100·Φ(x) − 50, giving [-50, +50] where 0 = no trend, < 0 = downtrend, > 0 = uptrend. ATR normalization adapts to volatility, and the R² weighting pulls weak fits toward 0 regardless of raw slope.

ADX

Average Directional Index — trend strength (direction-agnostic) on a 0–100 scale.

ADX().calculate(period, high, low, close)    # e.g. (14, high, low, close)

period — DM/ATR lookback. In: high, low, close · Out: array, 0–100; fully smoothed from bar 2·period onward.

ADX measures the strength of a trend (not its direction), on a 0–100 scale. It derives directional movement (DM+, DM−) from successive high/low changes — keeping only the larger per bar — and normalizes by True Range to form the directional indicators DI+ and DI−. The raw ADX is 100 · |DI+ − DI−| / (DI+ + DI−). The implementation uses three stages: cumulative sums over the first period bars, transitional averaging over the next period − 1, then exponential smoothing with factor (period − 1)/period applied to the directional components and to the ADX line itself. Interpretation: ADX 0–25 = weak trend, 50+ = strong; inspect DI+ vs DI− separately for direction. Typical period is 14.

AROON

How recently the highest high / lowest low occurred within the lookback.

AROON().calculate(variant, lookback, high, low)    # e.g. ("up", 25, high, low)

variant"up"/"down" (0–100) or "diff" (−100..+100) · lookback — scan window. In: high, low · Out: array (range depends on variant).

AROON tracks how recently the highest high and lowest low occurred within the window: Aroon_Up = 100 · (lookback − bars_since_highest_high) / lookback and Aroon_Down = 100 · (lookback − bars_since_lowest_low) / lookback, each in 0–100; the "diff" variant returns Up − Down (−100..+100). Values near 100 mean a very recent extreme (strong directional bias); near 0 means the extreme was lookback bars ago (waning trend). In the diff variant, zero-crossings mark momentum reversals. The first bar is seeded to 50 (up/down) or 0 (diff) for lack of history. Typical lookback is 14 or 25.

FTI

Govinda Khalsa's Follow-Through Index — finds the dominant cycle by testing symmetric lowpass filters over a band of periods.

FTI().calculate(variant, lookback, half_len, min_period, max_period, close)   # e.g. ("best_fti", 128, 32, 5, 65, close)

variant"lowpass" / "best_period" / "best_width" / "best_fti" · lookback — processing block · half_len — FIR half-width (2·half_len ≥ max_period) · min_period, max_period — period band. In: close · Out: array ("best_fti"[-50, +50]); first min(lookback − 1, N) bars are 0.

The Follow-Through Index gauges trend strength by comparing the mean size of significant price moves (turning-point "legs") to the channel width. It applies a symmetric FIR lowpass filter at each test period over log prices, finds direction reversals in the filtered signal, and measures legs above a noise threshold (20% of the longest move). FTI is mean_leg / channel_width (the width being the beta-fractile of |price − filtered| via a partition index with beta = 0.95), normalized through the regularized incomplete gamma CDF as 100 · gammainc(2, fti/3) − 50 for a bounded [-50, +50] score. It auto-selects the best period in [min_period, max_period], adapting to changing market rhythm. Other variants expose the filtered value, the chosen period, or the channel width.


Ehlers cycle & adaptive

from indicators.trend import ... — John Ehlers' cycle toolkit (2013).

ROOFING_FILTER

Two-pole highpass + Super Smoother lowpass → a clean, roughly zero-centred passband.

ROOFING_FILTER().calculate(lp_period, hp_period, close)    # e.g. (40, 80, close)

lp_period — Super Smoother period · hp_period — highpass period (detrend). In: close · Out: array (also on .result); bars 0–1 are 0.

The Roofing Filter is Ehlers' two-stage bandpass that isolates a chosen frequency band by removing both long-term trend and short-term noise. A two-pole highpass (period hp_period) first removes cycles longer than that period (detrending); the result is then smoothed by a Super Smoother lowpass (period lp_period) to suppress cycles shorter than that. The output is a roughly zero-centred smooth signal capturing intermediate-band momentum — positive = upward, negative = downward. Ehlers' defaults are hp_period=80, lp_period=40; widen or narrow the band by adjusting them. The first two bars are 0 (filter initialization starts the loop at index 2).

ADAPTIVE_RSI

RSI computed over half the autocorrelation-detected dominant cycle, Super-Smoothed.

ADAPTIVE_RSI().calculate(avg_len, close)    # e.g. (3, close)

avg_len — autocorrelation averaging length (Ehlers uses 3; 0 = per-lag). In: close · Out: array, ≈[0, 1] (the Super Smoother can overshoot slightly; overbought 0.7 / oversold 0.3); also on .result.

Ehlers' Adaptive RSI tunes the RSI period to the price series' dominant cycle. The price is first run through a roofing filter — a 48-bar two-pole highpass followed by a 10-bar Super Smoother — then an autocorrelation periodogram detects the dominant cycle each bar. RSI is computed on the filtered data over half the detected cycle, as up_sum / (up_sum + down_sum) (sums of positive/negative filtered differences), and smoothed again with the Super Smoother. Output sits near [0, 1] with 0.7/0.3 as overbought/oversold and ~0.5 neutral. Set avg_len to 3 (Ehlers' default) or 0 to average each lag over its own length.

ADAPTIVE_STOCHASTIC

Stochastic over one full dominant cycle (roofing-filtered), Super-Smoothed.

ADAPTIVE_STOCHASTIC().calculate(avg_len, close)    # e.g. (3, close)

avg_len — autocorrelation averaging length (3; 0 = per-lag). In: close · Out: array, ≈[0, 1] (can overshoot slightly; 0.7 / 0.3 guides); also on .result.

Measures overbought/oversold conditions using a stochastic computed over the dominant cycle rather than a fixed lookback. The price is roofing-filtered (48-bar highpass + 10-bar Super Smoother), an autocorrelation periodogram detects the dominant cycle length each bar, and the stochastic (current − lowest) / (highest − lowest) is taken over that cycle window and Super-Smoothed. Output is approximately [0, 1] (the Super Smoother can overshoot slightly), with 0.7/0.3 as Ehlers' overbought/oversold guides. avg_len controls autocorrelation averaging (typical 3, or 0 for per-lag). In degenerate flat windows during warm-up, the previous stochastic value is held.

ADAPTIVE_BANDPASS

Bandpass filter tuned to the dominant cycle, normalized by a fast AGC peak detector.

bp   = ADAPTIVE_BANDPASS()
sig  = bp.calculate(bandwidth, avg_len, close)    # e.g. (0.3, 3, close)
lead = bp.lead_signal                             # the advanced "lead" series

bandwidth — fraction of centre frequency (Ehlers 0.3) · avg_len — autocorrelation averaging (3; 0 = per-lag). In: close · Out: Signal array ≈[-1, +1]; also sets .lead_signal (a more aggressively-AGC'd lead version) and .result.

Isolates the dominant cyclical oscillation in price. A roofing filter (48-bar highpass + 10-bar Super Smoother) extracts the cyclic component; an autocorrelation periodogram detects the dominant period each bar; a bandpass filter tuned to 90% of that period isolates its energy; and a fast AGC (decaying peak detector, decay 0.991) normalizes the result to roughly [-1, +1]. The returned Signal oscillates around zero as cycles strengthen and weaken. The .lead_signal attribute is an advanced companion (1.3× the 4-bar-average difference of the signal, normalized by its own AGC with decay 0.93 and scaled by 0.7) — useful as an early cross against the signal. bandwidth (≈0.3) sets filter width; avg_len (≈3, or 0) sets autocorrelation averaging. ~2-bar warm-up.

EVEN_BETTER_SINEWAVE

Ehlers' EBSW: one-pole highpass + Super Smoother, power-normalized; hugs +1 in uptrends and −1 in downtrends, crossing zero at turns.

EVEN_BETTER_SINEWAVE().calculate(duration, close)    # e.g. (40, close)

duration — highpass period (≈20 swing, 40 default, 160 long trend). In: close · Out: array, [-1, +1]; also on .result.

EVEN_BETTER_SINEWAVE detects trend direction and mode changes by separating price into cyclical and noise components. A one-pole highpass removes cycles longer than duration, followed by a 10-bar Super Smoother that damps short-period noise. The smoothed wave is then power-normalized: output = wave / sqrt(pwr), where wave and pwr are 3-bar rolling averages of the smoothed amplitude and its square. The output is bounded to ≈[-1, +1], hugging +1 in uptrends, −1 in downtrends, and crossing zero at reversals. Use duration ≈ 40 (default), ~20 for swing trading, ~160 for long-term trends; valid after ~2 warm-up bars.

INVERSE_FISHER_STOCHASTIC

Inverse Fisher transform tanh(3v) of the Adaptive Stochastic — saturates hard toward the ±1 bounds and sharpens transitions.

INVERSE_FISHER_STOCHASTIC().calculate(avg_len, close)    # e.g. (3, close)

avg_len — autocorrelation averaging length (3). In: close · Out: array, (-1, +1); also on .result.

Applies Ehlers' inverse Fisher transform to the Adaptive Stochastic to sharpen momentum signals. It first computes the Adaptive Stochastic (a dominant-cycle-normalized oscillator of roofing-filtered price, ~0–1), recenters it to [-1, 1] (multiply by 2, offset by −1 via the −0.5 shift), and applies tanh(3v) = (exp(6v)−1)/(exp(6v)+1). This nonlinearity saturates output hard toward ±1, dramatically sharpening swings and quieting noise near zero-crossings. Positive = upward momentum, negative = downward. avg_len is the autocorrelation averaging length of the underlying Adaptive Stochastic (default 3, or 0 for per-lag). Best for spotting sharp regime changes and divergences rather than precise overbought/oversold levels.

DECYCLER_OSCILLATOR

Difference of two two-pole highpass filters — keeps the trend band between the two periods.

DECYCLER_OSCILLATOR().calculate(hp1, hp2, close)    # e.g. (30, 60, close)

hp1 — shorter highpass period · hp2 — longer highpass period. In: close · Out: array, zero-centred (price units); also on .result; bars 0–1 are 0.

The Decycler Oscillator (Ehlers, 2013) reveals trend by isolating a frequency band: it takes the difference of two two-pole highpass filters, output = HP(hp2) − HP(hp1). Each highpass passes cycles shorter than its period and attenuates longer ones, so the difference rejects both very short cycles (below hp1) and very long cycles (above hp2), leaving the intermediate trend-revealing band. The output is zero-centred in price units — sign gives trend direction, magnitude gives strength. Defaults are hp1=30, hp2=60; smaller periods isolate shorter-term trends. Output starts at the third bar (two-bar lookback in the highpass recursion).


Deviation & expectation

from indicators.deviationExpectation import ...

cmma

(function) Close minus its moving average (on log price), normalized by a log-ATR scaled by sqrt(lookback + 1), then CDF-mapped. A mean-reversion gauge.

from indicators.deviationExpectation import cmma
cmma(ohlc, lookback, atr_lookback=168)    # e.g. cmma(df, 14, 168)

ohlc — pandas DataFrame with high, low, close · lookback — MA window · atr_lookback — ATR window (default 168). Out: pandas Series, [-50, +50]; warm-up bars are 0.

cmma measures the normalized distance between the current log-close and a log-space moving average of the previous lookback bars. The deviation log_close − ma is normalized by atr · sqrt(lookback + 1.0) to account for volatility, then mapped through the normal CDF as 100 · norm.cdf(z) − 50, giving a bounded (-50, +50) probabilistic gauge. Positive = price above its moving average (bullish deviation); negative = below (underperformance). The output is stationary and useful for regime-relative momentum / mean-reversion. The first max(lookback, atr_lookback) bars are set to 0 during warm-up.

POLY_DEVIATION

Deviation of price from a fitted Legendre polynomial trend, scaled by the window's RMS error and CDF-compressed.

POLY_DEVIATION().calculate(degree, lookback, close)    # e.g. ("1", 20, close)

degreestring "1"/"2"/"3" · lookback — fit window (min 3/4/5 by degree). In: close · Out: array, ≈[-50, +50]; leading bars 0.

POLY_DEVIATION measures how far the current price sits from a polynomial trend fitted to log prices, normalized by the historical fit error. It fits a linear, quadratic, or cubic orthonormal Legendre polynomial to the window and computes the signed deviation (log_price − predicted) / rms_error (a z-score against typical fit error). This is mapped through a scaled normal CDF, 100 · norm.cdf(0.6 · z) − 50, compressing it to ≈[-50, +50]. Positive = price above trend (bullish), negative = below; magnitude reflects how extreme the deviation is. Minimum lookback is enforced (3/4/5 for degree 1/2/3) for a stable fit; the first lookback − 1 bars are 0.

PRICE_CHANGE_OSCILLATOR

Short- vs long-window average absolute log price change, ATR-normalized and CDF-mapped.

PRICE_CHANGE_OSCILLATOR().calculate(short, mult, open_, high, low, close)    # e.g. (14, 2, o, h, l, c)

short — short window · mult — long = short × mult (min 2). In: OHLC · Out: array, ≈[-50, +50]; first long bars are 0.

The Price Change Oscillator compares short- vs long-term absolute price changes relative to volatility. It takes the difference of the mean log-return magnitude over a short window minus that over a longer window (long = short × mult), normalized by ATR with an adaptive denominator 0.36 + 1.0/short + 0.7·log(0.5·mult)/1.609. The normalized difference is mapped through the normal CDF via 100 · Φ(4 · ratio) − 50, giving ≈[-50, +50]. Above 0, recent price action is more active than the long-term baseline (momentum); below 0 suggests mean reversion. mult sets the long/short ratio and is clamped to a minimum of 2; the first long bars are 0.

VARIANCE_RATIO

Ratio of short- to long-window variance, transformed through the F-distribution CDF.

VARIANCE_RATIO().calculate(variant, short, mult, close)    # e.g. ("price", 5, 4, close)

variant"price" (variance of log prices) or "change" (of log changes) · short — short window · mult — long = short × mult. In: close · Out: array, [-50, +50]; leading bars 0.

The Variance Ratio contrasts short- vs long-term volatility to flag trending vs mean-reverting regimes. It takes the ratio of variance over short to variance over short × mult, then maps it through the F-distribution CDF with mode-specific degrees of freedom. In "change" mode the raw ratio is used directly with df=(4, 4·mult); in "price" mode the ratio is first scaled by mult and uses df=(2, 2·mult). The result is 100 · CDF − 50, spanning ≈[-50, +50]: positive = short-term volatility exceeds long-term (trending/divergence), negative = consolidation/mean reversion. If the long-window variance is ≤ 0 (rare), the ratio defaults to 1.0. Warm-up is long − 1 bars (price) or long bars (change). Typical: short 5–10, mult 2–4.


Information content

from indicators.informationContent import ...

entropy

(function) Normalized Shannon entropy of the price histogram — 0 = ordered/predictable, 1 = maximally random.

from indicators.informationContent import entropy
entropy(x)

x — price series. Out: scalar float [0, 1]. Skips the first 168 points; bin count adapts to length.

entropy quantifies the disorder of the price distribution — whether prices cluster in narrow ranges (low entropy) or spread across their historical range (high entropy). It histogram-bins the input (after skipping the first 168 warm-up observations), forms bin proportions p_i, and sums −p_i · log(p_i) over non-empty bins. The bin count adapts: 3 for n<100, 5 for n<1000, 10 for n<10000, 20 for n≥10000. Dividing by log(nbins) normalizes the result to [0, 1], where 0 = all prices in one bin and 1 = uniform spread. Higher entropy suggests random / trendless action; lower suggests tight, potentially consolidating ranges.

ENTROPY

Rolling entropy of up/down move patterns ("words"), CDF-normalized — a per-bar series.

ENTROPY().calculate(word_len, mult, close)    # e.g. (2, 3, close)

word_len — bits per pattern · mult — data-requirement multiplier. In: close · Out: array, ≈[-50, +50]; first min(2^word_len · mult, N − 1) bars are 0.

ENTROPY measures the information content of price movement by computing the Shannon entropy of up/down bit patterns. Price changes are encoded as binary words of word_len bits (1 = rise, 0 = fall) and the Shannon entropy across observed words is computed, normalized to [0, 1] by dividing by log(2^word_len). A shaping transform follows — for word_len == 1 it uses 1 − exp(log(1.00000001 − value)/5) (hardcoded exponent 5, with the 1e-8 guard against log(0)); for word_len > 1, 1 − exp(log(1 − value)/word_len) — centered on an adaptive mean (0.6 for word_len==1, else 1/word_len + 0.35). The result is mapped through the normal CDF as 100 · norm.cdf(8·(value − mean)) − 50, giving a [-50, +50] oscillator: values near 0 indicate random movement, large deviations flag predictable (low-entropy) patterns.

rangeInterQuartileRangeRatio

(function) Ratio of the mean to the interquartile range — a scale-invariant spread metric.

from indicators.informationContent import rangeInterQuartileRangeRatio
rangeInterQuartileRangeRatio(x)

x — price/indicator series. Out: scalar float. Skips the first 168 points.

This metric relates a series' mean level to its dispersion: mean / IQR, where IQR is the 75th-minus-25th percentile spread. A larger absolute value means a larger mean relative to the middle-50% spread (from a strong mean and/or a concentrated distribution). It runs on the window after discarding the first 168 bars as warm-up. Interpretation is context-dependent: a high positive ratio suggests a strong uptrend relative to central dispersion, a low positive one indicates greater relative spread; a negative mean yields a negative ratio (uncommon in raw price series).

MUTUAL_INFORMATION

Mutual information between the next move and the preceding up/down pattern, CDF-normalized.

MUTUAL_INFORMATION().calculate(word_len, mult, close)    # e.g. (3, 2, close)

word_len — pattern length · mult — scaling / min count per bin. In: close · Out: array, [-50, +50]; leading bars 0.

MUTUAL_INFORMATION quantifies how much a history "word" of preceding up/down moves predicts the next move. Prices are converted to binary up/down comparisons, grouped into words of length word_len, and mutual information MI = Σ p(w,c) · log(p(w,c) / (p(w)·p(c))) is computed over word/next-move combinations, then normalized via 100 · Φ(3·(MI · mult · √word_len − 0.12·word_len − 0.04)) − 50 to a bounded [-50, +50]. Positive values mean history biases toward up moves, negative toward down, near-zero = weak predictability. Requires 2^(word_len+1) · mult + 1 warm-up bars; typical word_len 1–3, with mult requiring that many occurrences per history bin.


Volume

from indicators.volume import ...

VSA

Volume-Spread Analysis — regresses bar range (÷ATR) on volume (÷rolling median); the output is the residual (actual minus volume-expected range): positive = unusually wide, negative = compressed.

VSA().calculate(norm_lookback, open_, high, low, close, volume)    # e.g. (14, o, h, l, c, v)

norm_lookback — ATR / median / regression window. In: OHLCV · Out: array (on .result); first 2·norm_lookback bars are NaN.

Volume-Spread Analysis relates price range to volume to spot momentum divergences and shifting supply/demand. It normalizes the bar's range high − low by ATR (norm_range) and volume by its rolling median (norm_volume), then runs a rolling linear regression of norm_range on norm_volume over the lookback. If the regression slope is positive and the correlation coefficient magnitude is ≥ 0.2, the output is the deviation of the actual normalized range from its volume-predicted value: positive = wider range than volume implies (potential strength), negative = narrower (potential weakness). Output is 0 when the slope is ≤ 0 or |r| < 0.2, and NaN during the ~2·norm_lookback-bar warm-up.

INTRADAY_INTENSITY

100·(2·close − high − low)/(high − low) · volume, moving-averaged; if smooth > 1 it is normalized by smoothed volume (an oscillator).

INTRADAY_INTENSITY().calculate(ma_len, smooth, high, low, close, volume)    # e.g. (14, 1, h, l, c, v)

ma_len — MA window · smooth — volume EMA (≤ 1 = none). In: high, low, close, volume · Out: array; leading bars 0.

Intraday Intensity relates where a bar closes within its range to its volume. Each bar computes 100 · (2·close − high − low)/(high − low) · volume: the intensity component (2·close − high − low)/(high − low) is in [-1, +1] (×100 → ±100) and is then multiplied by volume, so the raw per-bar value is volume-weighted and unbounded. The result is averaged over ma_len bars; when smooth > 1, it is divided by an EMA of volume (alpha = 2/(smooth+1)), recovering a normalized intensity-like oscillator. Positive = bullish (closes high in the range on strong volume), negative = bearish; equal high/low gives 0. Warm-up zeros run to min(ma_len − 1 + first_volume, N).

MONEY_FLOW

Chaikin's Money Flow — MA of intraday-intensity ÷ MA of volume.

MONEY_FLOW().calculate(lookback, high, low, close, volume)    # e.g. (20, h, l, c, v)

lookback — averaging window. In: high, low, close, volume · Out: array; leading bars 0.

Chaikin's Money Flow measures buying vs selling pressure by combining the close's position in the range with volume. For each bar (from the first non-zero-volume bar) it computes intraday intensity 100 · (2·close − high − low)/(high − low) · volume — where the close sits in the range (±100) weighted by volume; equal high/low gives 0. It then divides a lookback-period moving average of that intensity by the moving average of volume over the same window. Positive = accumulation (closes in the upper range with volume), negative = distribution. Bounds depend on the volume scale but it oscillates around zero. Warm-up zeros span the first non-zero-volume bar plus lookback − 1.

REACTIVITY

Volume-weighted range "reactivity" × price change, normalized by smoothed range and CDF-mapped.

REACTIVITY().calculate(lookback, mult, high, low, close, volume)    # e.g. (20, 1, h, l, c, v)

lookback — range / change window · mult — smoothing-constant multiplier. In: high, low, close, volume · Out: array, [-50, +50]; leading bars 0.

REACTIVITY gauges how aggressively price moves relative to recent volatility and volume. It forms an aspect ratio of the current range vs a smoothed range, scaled by the current-vs-smoothed volume ratio, then multiplies by the lookback price change. That raw reactivity is normalized by the smoothed range and mapped through the normal CDF with scale 0.6, yielding output bounded exactly in [-50, +50]. Positive = upward reactivity, negative = downward, near-zero = muted response. The exponential smoothing constant is alpha = 2.0 / (lookback · mult + 1); lookback sets the window and mult the smoothing weight. Warm-up begins after the first non-zero-volume bar plus lookback.

PRICE_VOLUME_FIT

Rolling regression slope of log(close) on log(volume) — price-to-volume elasticity, CDF-mapped.

PRICE_VOLUME_FIT().calculate(lookback, close, volume)    # e.g. (20, close, volume)

lookback — regression window. In: close, volume · Out: array, [-50, +50]; leading bars 0.

PRICE_VOLUME_FIT measures the strength and direction of the price–volume relationship. For each bar it regresses log(close) on log(volume + 1) over a rolling lookback window and maps the regression coefficient through 100 · norm.cdf(9.0 · coef) − 50 to a bounded [-50, +50]. Positive = price tends to rise with volume; negative = an inverse relationship; 0 = neutral. The 9.0 scaling plus the CDF turn the raw slope into a stationary, comparable reading. Warm-up is automatic: output is 0 until both the window is full and the first non-zero-volume bar is seen, accommodating data with missing early volume.

VOLUME_WEIGHTED_MA_RATIO

Ratio of the volume-weighted MA to the simple MA, log-scaled and CDF-normalized.

VOLUME_WEIGHTED_MA_RATIO().calculate(lookback, close, volume)    # e.g. (20, close, volume)

lookback — MA window. In: close, volume · Out: array, ≈[-50, +50]; leading bars 0.

VOLUME_WEIGHTED_MA_RATIO measures whether volume concentrates at higher or lower prices over the window. It computes 1000 · log(lookback · numer / (total · denom)) / sqrt(lookback), where numer = Σ(volume·close), total = Σ(volume), denom = Σ(close) — essentially the log-ratio of the volume-weighted average price to the simple average — then maps it through the normal CDF as 100 · cdf(z) − 50, bounding output to [-50, +50]. Positive = volume concentrated on higher prices (potential strength); negative = on lower prices (potential weakness). Output is 0 until enough non-zero-volume data accrues (about lookback − 1 bars after the first non-zero-volume bar).

ON_BALANCE_VOLUME

Signed-volume momentum over a window, volume-normalized and CDF-mapped to [-50, +50].

ON_BALANCE_VOLUME().calculate(variant, lookback, delta_lag, close, volume)    # e.g. ("normalized", 20, 0, c, v)

variant"normalized" or "delta" (difference vs delta_lag bars ago) · lookback — window · delta_lag — used only for "delta". In: close, volume · Out: array, [-50, +50]; leading bars 0.

On-Balance Volume measures the directional flow of volume. Over the last lookback bars it accumulates volume as positive when close > prior close (buying pressure) and negative when close < prior close (selling), divides by total volume, scales by sqrt(lookback), and normalizes through the normal CDF as 100 · cdf(0.6 · value) − 50, giving a zero-centred [-50, +50] reading. Positive = net buying volume, negative = net selling, 0 = balanced. Bars before the first non-zero volume are skipped. With variant="delta", the output switches to a momentum form: the change in this value from delta_lag bars earlier.

VOLUME_INDEX

Sum of log-returns on volume-up ("positive") or volume-down ("negative") bars, volatility-normalized and CDF-mapped.

VOLUME_INDEX().calculate(variant, lookback, close, volume)    # e.g. ("positive", 20, c, v)

variant"positive" / "negative" · lookback — accumulation window. In: close, volume · Out: array, [-50, +50]; leading bars 0.

VOLUME_INDEX measures price movement that occurs on volume changes. The "positive" variant accumulates log returns on bars where volume rises; "negative" accumulates returns where volume falls. The sum is normalized by sqrt(lookback) (period-independence), then divided by historical volatility (std of close over a window of at least 250 bars) to adjust for regime, and finally mapped through the normal CDF as 100 · Φ(0.5·x) − 50, an oscillator in [-50, +50] centered at 0. Positive = sustained moves on increasing (positive) or decreasing (negative) volume; values near ±50 signal strong volume–price alignment. Needs ≥250 bars of history plus the first non-zero-volume position; outputs 0 where data or volatility is insufficient.

VOLUME_MOMENTUM

Short- vs long-window average volume, as a log-ratio, CDF-mapped. (Volume only.)

VOLUME_MOMENTUM().calculate(short, mult, volume)    # e.g. (10, 2, volume)

short — short window · mult — long = short × mult (min 2). In: volume · Out: array, [-50, +50]; leading bars 0.

Volume Momentum compares short- to long-term average volume — is volume accelerating or decelerating? It computes log(short_avg / long_avg) (where long = short × mult), normalized by a scale factor exp(log(mult)/3.0), then mapped through the normal CDF and scaled as 100 · cdf(3 · normalized_log) − 50, giving a zero-centred reading in ≈[-50, +50]. Above 0 = short-term volume strength relative to the long-term average; below 0 = relative weakness. short sets the short window and mult defines the long one (clamped to a minimum of 2). Leading bars up to the long lookback (after the first non-zero volume) are 0.


Multi-market

JANUS

Gary Anderson's Janus family — relative strength (RS) and relative momentum (RM) of each market in a universe against a median index, plus leader/laggard equity, RSS, DOM/DOE and CMA trend signals. Operates on a 2-D close matrix.

from indicators.multiMarket import JANUS
JANUS().calculate(var_num, lookback, market_or_smooth, delta_lag, closes)   # e.g. ("rss", 20, 5, 3, closes)

var_num — output selector (e.g. "raw_rs", "fractile_rs", "delta_fractile_rs", "rss", "delta_rss", "dom", "doe", "raw_rm", "fractile_rm", "rs_leader_equity", "rs_laggard_equity", "rm_ps", "cma_oos", "leader_cma_oos", …) · lookback — window · market_or_smooth — 1-based market number for per-market variants (0 = index for dom/doe), or the smoothing length for rss/delta_rss · delta_lag — differencing lag for the delta_* variants · closes2-D array (nbars, n_markets). Out: array (nbars,); first lookback bars are 0.

JANUS measures how each market in a universe performs relative to the median index. It computes offensive/defensive relative strength as 70.710678 · (market_off/index_off − market_def/index_def), where bars are split by whether returns exceeded the period median (offensive ≥ median, defensive < median), capped at ±200. From this it derives Direction of Momentum (DOM) and Direction of Entropy (DOE) — cumulative returns while the relative-strength spread (RSS, the gap between strongest and weakest performers) expands or contracts — and Relative Momentum (RM), the same strength calculation applied to DOM changes (capped at ±300). Both RS and RM are rank-transformed to fractiles (0–1). Variants expose raw/fractile strength, spreads and their changes, out-of-sample leader/laggard equity, and a conditional moving-average (CMA) system that trades only when DOM exceeds its adaptive EMA. lookback (≈5–50) sets the strength window; the first lookback bars are 0.

JanusEngine (the underlying engine) is also exported if you want the full set of computed series at once rather than one var_num at a time.


Utilities

CommonMethods

from indicators.common.common import CommonMethods — shared building blocks used by the indicators above. These helpers take no self; call them on the class directly:

from indicators.common.common import CommonMethods

tr  = CommonMethods.true_range_series(use_log, high, low, close)            # per-bar true range, len N
atr = CommonMethods.atr_series(use_log, length, open_, high, low, close)    # rolling ATR, len N (NaN before `length`)
a   = CommonMethods.atr_single(use_log, icase, length, open_, high, low, close)   # scalar ATR at bar icase
v   = CommonMethods.variance(use_change, icase, length, prices)             # scalar historical variance
c1, c2, c3 = CommonMethods.legendre_3(n)                                    # orthonormal Legendre coefficients
  • use_log — work in log space (ratios) when truthy, else absolute differences.
  • use_change — variance of log changes (truthy) vs log prices.
  • In the ATR helpers open_ is accepted but unused (true range needs only high/low/close).
  • atr_series is NaN before index length; atr_single requires icase >= length.

CommonMethods provides the foundational computations the indicators reuse. true_range_series gives per-bar true range (bar 0 uses only high − low; later bars use the max of high − low, high − prior_close, prior_close − low). The ATR helpers average true range over a length window (arithmetic or log-ratio via use_log), with the first valid value at index length (earlier entries are NaN). variance returns sample variance of log prices or log changes over the window, and legendre_3 returns first-, second-, and third-order Legendre coefficients each normalized to unit length (c3 is orthogonalized against c1). All methods take NumPy arrays and return new arrays.


License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

indicatorpy-0.2.1.tar.gz (78.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

indicatorpy-0.2.1-py3-none-any.whl (67.8 kB view details)

Uploaded Python 3

File details

Details for the file indicatorpy-0.2.1.tar.gz.

File metadata

  • Download URL: indicatorpy-0.2.1.tar.gz
  • Upload date:
  • Size: 78.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.7

File hashes

Hashes for indicatorpy-0.2.1.tar.gz
Algorithm Hash digest
SHA256 94e0326c999b56df41c2a496462bef8943699d9710072cb80be832941c9df069
MD5 62603a01784621986b3c8a07490c6db5
BLAKE2b-256 7bd7042ecd20cd11ba653f7044d8f4d99c5e58cacbad4b6a7046e10e7dd651a4

See more details on using hashes here.

File details

Details for the file indicatorpy-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: indicatorpy-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 67.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.7

File hashes

Hashes for indicatorpy-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e1b777fb63fd9a95d40357e24bbe491a7973b9841044733ed30482526f719638
MD5 916a642e7206e81bdd47699c8ea1eb8b
BLAKE2b-256 e1bdedd933425b9e656acf30a3b6111936486a0c6088713eb3fc344590dbab08

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page