Indicators
The quaver.strategies.indicators module provides a pure-NumPy library of
technical-analysis primitives shared by the bundled strategy engines. All
functions accept one-dimensional numpy.ndarray inputs and return arrays
of the same length, padding leading positions with NaN when insufficient
history is available.
Note
The indicator API has no dependency on pandas, talib, or any
other charting library. This keeps the strategy layer light and
deterministic across platforms.
Overview
The 21 indicators fall into five families.
Trend / smoothing
Simple Moving Average — arithmetic mean over a rolling window. |
|
Exponential Moving Average — weighted toward recent values. |
|
Wilder smoothing — slower EMA variant used in ADX / RSI. |
|
Moving Average Convergence Divergence (MACD line, signal, histogram). |
Volatility / range
Per-bar True Range (max of three high / low / prev-close ranges). |
|
Average True Range — Wilder-smoothed True Range. |
|
Upper, middle, and lower Bollinger Bands. |
|
Normalised band width — proxy for volatility regime. |
|
Donchian channel — rolling max / min of high / low. |
|
Keltner channel — EMA ± ATR multiple. |
Momentum / oscillators
Relative Strength Index — 0–100 momentum oscillator. |
|
Average Directional Index — trend-strength magnitude. |
|
Stochastic %K and %D oscillators. |
|
Commodity Channel Index — deviation from typical price. |
Volume / flow
On-Balance Volume — cumulative signed volume. |
|
Volume-Weighted Average Price (rolling). |
|
Current volume as a multiple of recent average. |
Statistical helpers
Bar-over-bar percentage returns. |
|
Rolling maximum over a window. |
|
Rolling minimum over a window. |
|
Rolling quantile of a series. |
Conventions
NaN padding — every indicator pads leading positions with
NaNuntil enough history exists. Strategy code should filter or guard againstNaNbefore making decisions.No look-ahead — indicators only consume values up to and including the current index; the engine layer is responsible for excluding the current bar when point-in-time evaluation is required.
Float64 throughout — inputs are expected to be
np.float64arrays. Passdf["close"].to_numpy(dtype=float)from a pandas DataFrame.
Reference
Shared pure-numpy indicator library for strategy engines.
This module provides a collection of technical analysis indicator functions
implemented exclusively with NumPy. It is intended to be imported by any
strategy that requires price-based signal computation. All functions operate on
numpy.ndarray objects and return arrays of the same length as their
primary input, padding leading positions with NaN wherever insufficient
data exists to produce a meaningful value.
- quaver.strategies.indicators.sma(values, period)[source]
Compute the Simple Moving Average using convolution.
Calculates a rolling arithmetic mean of period consecutive elements via
numpy.convolve(). Positions0throughperiod - 2are filled withNaNbecause fewer than period samples are available.- Parameters:
- Returns:
Array of the same length as values containing the SMA values, with
NaNfor the firstperiod - 1positions.- Return type:
- quaver.strategies.indicators.true_range(high, low, close)[source]
Compute the True Range for each bar.
The True Range at index i is defined as:
TR[i] = max(H[i] - L[i], |H[i] - C[i-1]|, |L[i] - C[i-1]|)
Index 0 is always
NaNbecause no previous close exists.- Parameters:
- Returns:
Array of the same length as high containing True Range values, with
NaNat index 0.- Return type:
- quaver.strategies.indicators.wilder_smooth(values, period)[source]
Apply Wilder’s smoothing (RMA) to an array.
The seed value is the arithmetic mean of the first period valid elements (indices
1throughperiod, because index 0 isNaNfor True Range and Directional Movement series). Subsequent values are computed as:out[i] = out[i-1] * (period - 1) / period + values[i]
Note
CORRECTNESS NOTE: The seed must use
numpy.mean(), notnumpy.sum(). Usingnp.sumproduces a seed that is period times too large and corrupts all downstream ATR and ADX values.- Parameters:
- Returns:
Array of the same length as values containing the smoothed values, with
NaNfor all positions before the first valid seed.- Return type:
- quaver.strategies.indicators.adx(high, low, close, period=14)[source]
Compute Wilder’s Average Directional Index (ADX) and Directional Indicators.
Implements the full ADX calculation pipeline:
Computes raw Directional Movement (+DM and -DM).
Smooths True Range and Directional Movement with
wilder_smooth().Derives +DI and -DI as percentages of smoothed ATR.
Computes DX from the divergence of +DI and -DI.
Smooths DX with a second Wilder pass to produce the ADX line.
At least
2 * period + 1bars are required; a tuple of three all-NaNarrays is returned when this threshold is not met.- Parameters:
high (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar high prices.low (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar low prices.close (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar closing prices.period (
int) – Wilder smoothing period. Defaults to14.
- Returns:
A three-element tuple
(adx_arr, plus_di, minus_di)whereadx_arr– Wilder-smoothed ADX values (0-100 scale).plus_di– Positive Directional Indicator (+DI, 0-100 scale).minus_di– Negative Directional Indicator (-DI, 0-100 scale).
All three arrays have the same length as high and contain
NaNwherever insufficient data is available.- Return type:
tuple[ndarray[tuple[Any,...],dtype[double]],ndarray[tuple[Any,...],dtype[double]],ndarray[tuple[Any,...],dtype[double]]]
- quaver.strategies.indicators.bollinger_bands(close, period=20, num_std=2.0)[source]
Compute Bollinger Bands around a Simple Moving Average.
The middle band is the SMA of close over period bars. The upper and lower bands are offset by num_std population standard deviations (
ddof=0) of the same rolling window:upper = SMA + num_std * std middle = SMA lower = SMA - num_std * std
- Parameters:
- Returns:
A three-element tuple
(upper, middle, lower)whereupper– Upper Bollinger Band array.middle– Middle band (SMA) array.lower– Lower Bollinger Band array.
All three arrays have the same length as close and contain
NaNfor the firstperiod - 1positions.- Return type:
tuple[ndarray[tuple[Any,...],dtype[double]],ndarray[tuple[Any,...],dtype[double]],ndarray[tuple[Any,...],dtype[double]]]
- quaver.strategies.indicators.bollinger_band_width(upper, middle, lower)[source]
Compute the Bollinger Band Width (BBW) normalised by the middle band.
Band width is calculated as:
BBW = (upper - lower) / middle
The result is
NaNwherever middle equals zero or any of the three input arrays carry aNaNvalue at that position.- Parameters:
upper (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of upper Bollinger Band values.middle (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of middle band (SMA) values.lower (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of lower Bollinger Band values.
- Returns:
Array of BBW values with the same length as upper, containing
NaNwhere middle is zero or any input isNaN.- Return type:
- quaver.strategies.indicators.rolling_percentile(values, window, percentile)[source]
Compute a rolling percentile over a fixed-size sliding window.
For each position i >=
window - 1, the percentile is computed overvalues[i - window + 1 : i + 1], excluding anyNaNelements within the window. Positions beforewindow - 1are filled withNaN.- Parameters:
- Returns:
Array of the same length as values containing the rolling percentile, with
NaNfor the firstwindow - 1positions or whenever the window contains no valid (non-NaN) values.- Return type:
- quaver.strategies.indicators.daily_returns(close)[source]
Compute bar-over-bar percentage returns.
Returns are defined as:
ret[t] = (close[t] - close[t-1]) / close[t-1]
Index 0 is always
NaNbecause no prior close is available. Positions where the previous close is zero are also set toNaNto avoid division-by-zero artefacts.
- quaver.strategies.indicators.volume_relative(volume, period=20)[source]
Compute the Relative Volume ratio against a simple moving average.
Relative Volume is defined as:
RVOL = volume / SMA(volume, period)
The result is
NaNwherever the SMA is zero orNaN(i.e. for the firstperiod - 1positions).- Parameters:
- Returns:
Array of the same length as volume containing the relative volume ratio, with
NaNfor the firstperiod - 1positions or wherever the SMA is zero.- Return type:
- quaver.strategies.indicators.atr(high, low, close, period=14)[source]
Compute the Average True Range using a simple moving average.
Calculates the True Range for each bar and then smooths it with a
sma()of length period. The first valid ATR value appears at indexperiod(because True Range index 0 isNaN).- Parameters:
high (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar high prices.low (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar low prices.close (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar closing prices.period (
int) – Rolling window length for the SMA smoothing. Must be >= 1. Defaults to14.
- Returns:
Array of the same length as high containing ATR values, with
NaNfor leading positions where insufficient data exists.- Return type:
- quaver.strategies.indicators.rsi(close, period=14)[source]
Compute the Relative Strength Index (SMA-based).
Uses simple moving averages of gains and losses over period bars:
RSI = 100 - 100 / (1 + avg_gain / avg_loss)
The first valid RSI appears at index period. Returns
100.0when average loss is zero (all gains).- Parameters:
- Returns:
Array of the same length as close containing RSI values in
[0, 100], withNaNfor the first period positions.- Return type:
- quaver.strategies.indicators.rolling_max(values, period)[source]
Compute the rolling maximum over a fixed-size sliding window.
For each position i >=
period - 1, the result is the maximum ofvalues[i - period + 1 : i + 1]. Leading positions areNaN.- Parameters:
- Returns:
Array of the same length as values containing rolling maximum values, with
NaNfor the firstperiod - 1positions.- Return type:
- quaver.strategies.indicators.rolling_min(values, period)[source]
Compute the rolling minimum over a fixed-size sliding window.
For each position i >=
period - 1, the result is the minimum ofvalues[i - period + 1 : i + 1]. Leading positions areNaN.- Parameters:
- Returns:
Array of the same length as values containing rolling minimum values, with
NaNfor the firstperiod - 1positions.- Return type:
- quaver.strategies.indicators.ema(values, period)[source]
Compute the Exponential Moving Average.
The seed value is the SMA of the first period elements. Subsequent values use the standard EMA recursion:
EMA[i] = close[i] * k + EMA[i-1] * (1 - k)
where
k = 2 / (period + 1).- Parameters:
- Returns:
Array of the same length as values with
NaNfor the firstperiod - 2positions (first valid value at indexperiod - 1).- Return type:
- quaver.strategies.indicators.macd(close, fast=12, slow=26, signal=9)[source]
Compute Moving Average Convergence Divergence.
The MACD line is the difference between the fast and slow EMAs. The signal line is an EMA of the MACD line. The histogram is their difference:
macd_line = EMA(close, fast) - EMA(close, slow) signal_line = EMA(macd_line, signal) histogram = macd_line - signal_line
- quaver.strategies.indicators.stochastic(high, low, close, period_k=14, period_d=3)[source]
Compute the Stochastic Oscillator (%K and %D).
%K measures where the close sits relative to the high-low range over period_k bars:
%K = (close - lowest_low) / (highest_high - lowest_low) * 100
%D is the SMA of %K over period_d bars.
- Parameters:
high (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar high prices.low (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar low prices.close (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar closing prices.period_k (
int) – Lookback period for %K. Defaults to14.period_d (
int) – SMA period for %D smoothing. Defaults to3.
- Returns:
(%K, %D)tuple. Both arrays have the same length as close.- Return type:
tuple[ndarray[tuple[Any,...],dtype[double]],ndarray[tuple[Any,...],dtype[double]]]
- quaver.strategies.indicators.obv(close, volume)[source]
Compute On-Balance Volume.
OBV is a cumulative total of volume, where volume is added on up-close bars and subtracted on down-close bars:
OBV[0] = NaN OBV[i] = OBV[i-1] + sign(close[i] - close[i-1]) * volume[i]
- Parameters:
- Returns:
Array of the same length as close with
NaNat index 0.- Return type:
- quaver.strategies.indicators.vwap(high, low, close, volume)[source]
Compute cumulative Volume-Weighted Average Price.
VWAP is the ratio of cumulative typical-price-weighted volume to cumulative volume:
TP = (high + low + close) / 3 VWAP = cumsum(TP * volume) / cumsum(volume)
- Parameters:
high (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar high prices.low (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar low prices.close (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar closing prices.volume (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar volumes.
- Returns:
Array of the same length as close.
NaNwherever cumulative volume is zero.- Return type:
- quaver.strategies.indicators.cci(high, low, close, period=20, scalar=0.015)[source]
Compute the Commodity Channel Index.
CCI measures how far the typical price deviates from its SMA, normalised by mean absolute deviation:
TP = (high + low + close) / 3 CCI = (TP - SMA(TP, period)) / (scalar * MAD(TP, period))
- Parameters:
high (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar high prices.low (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar low prices.close (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar closing prices.period (
int) – Lookback period. Defaults to20.scalar (
float) – Scaling constant. Defaults to0.015.
- Returns:
Array of the same length as close with
NaNfor the firstperiod - 1positions.- Return type:
- quaver.strategies.indicators.donchian(high, low, period=20)[source]
Compute Donchian Channels.
The upper channel is the rolling max of high and the lower channel is the rolling min of low. The middle is their average:
upper = rolling_max(high, period) lower = rolling_min(low, period) middle = (upper + lower) / 2
- quaver.strategies.indicators.keltner(high, low, close, period=20, multiplier=2.0)[source]
Compute Keltner Channels.
The middle band is an EMA of close. Upper and lower bands are offset by a multiple of the ATR:
middle = EMA(close, period) upper = middle + multiplier * ATR(high, low, close, period) lower = middle - multiplier * ATR(high, low, close, period)
- Parameters:
high (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar high prices.low (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar low prices.close (
ndarray[tuple[Any,...],dtype[double]]) – One-dimensional array of bar closing prices.period (
int) – EMA and ATR lookback period. Defaults to20.multiplier (
float) – ATR multiplier for channel width. Defaults to2.0.
- Returns:
(upper, middle, lower)tuple. All arrays have the same length as close.- Return type:
tuple[ndarray[tuple[Any,...],dtype[double]],ndarray[tuple[Any,...],dtype[double]],ndarray[tuple[Any,...],dtype[double]]]