Momentum and oscillators
Bounded and unbounded oscillators, momentum measures and divergence detection. See the indicator reference for how to read a signature, and Indicators for the construction and guarding rules.
CCI
Commodity channel index: (typical price − its mean) / (0.015 × mean deviation) over period.
new CCI(candleIndicator: Indicator<Candle>, period: number)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
Indicator<Candle> |
required | The candle series. |
period |
number |
required | The window length (20 classically). |
- Input: The candle series.
- Value (
getValue()afterhasValue()):number | undefined - Warm-up:
undefineduntilperiodcandles have been seen.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new CCI(candles, 20);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
manager.debugLog('CCI20', value);
});
ChandeMomentumOscillator
Chande momentum oscillator: 100 × (sum of gains − sum of losses) / (sum of gains + sum of losses) over length, between −100 and 100.
new ChandeMomentumOscillator(sourceIndicator: Indicator<number | undefined>, length: number = 9)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<number | undefined> |
required | The number stream. |
length |
number |
9 |
The window length. |
- Input: A number stream.
- Value (
getValue()afterhasValue()):number | undefined - Warm-up: Returns a number from the first candle,
NaNwhile there has been no price change; the window is complete afterlengthcandles.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new ChandeMomentumOscillator(candles.close, 9);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined || Number.isNaN(value)) return;
manager.debugLog('CMO', value);
});
Divergences
Finds regular and hidden divergences between price swings and a target series (an oscillator), using Swings pivots.
new Divergences(candleIndicator: Indicator<Candle>, source: 'close' | 'high/low', period: number, maxPivots: number, maxPivotAge: number, targetIndicator: Indicator<number | undefined>, minPivotAge: number)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
Indicator<Candle> |
required | The candle series. |
source |
'close' | 'high/low' |
required | Pivot price: 'close' or 'high/low'. |
period |
number |
required | Candles on each side that define a pivot. |
maxPivots |
number |
required | Maximum pivots kept. |
maxPivotAge |
number |
required | Pivots older than this many candles are dropped. |
targetIndicator |
Indicator<number | undefined> |
required | The series compared with price (for example new RSI(candles.close, 14)). |
minPivotAge |
number |
required | Minimum age, in candles, of the older pivot of a divergence. |
- Input: The candle series plus a target number stream (for example an RSI).
- Value (
getValue()afterhasValue()):DivergenceInfoindex: numberpositiveRegular: readonly Divergence[]positiveHidden: readonly Divergence[]negativeRegular: readonly Divergence[]negativeHidden: readonly Divergence[]
Divergenceis{ pivotIndex: number; pivotTime: number; pivotPrice: number; pivotTarget: number }.- Warm-up: Ready from the first candle, with empty lists until pivots exist; a pivot is only confirmed
periodcandles after it forms.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Divergences(candles, 'close', 5, 10, 60, new RSI(candles.close, 14), 5);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value.positiveRegular.length > 0) manager.debugLog('bullish divergence', value.positiveRegular.length);
});
DPO
Detrended price oscillator: close − SMA(period) as it was period / 2 + 1 candles ago.
new DPO(sourceIndicator: Indicator<number | undefined>, period: number = 20)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<number | undefined> |
required | The number stream. |
period |
number |
20 |
The SMA length. |
- Input: A number stream.
- Value (
getValue()afterhasValue()):number | undefined - Warm-up: First value on candle
period + floor(period / 2) + 1(31 for period 20).
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new DPO(candles.close, 20);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
manager.debugLog('DPO', value);
});
HARSI
JayRogers' Heikin Ashi RSI Oscillator: Heikin Ashi candles of zero-centred RSIs (RSI − 50, from −50 to +50; its levels are ±20 and ±30). The open is the previous candle's close RSI, the high and the low the higher and the lower of the high's and the low's RSIs.
new HARSI(candleIndicator: ICandleIndicator, period: number, openSmoothingLength: number = 1)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
period |
number |
required | The RSI length. |
openSmoothingLength |
number |
1 |
How many times the Heikin Ashi open weighs the previous open against the previous close (1, the classic Heikin Ashi open, by default). |
- Input: The candle series.
- Value (
getValue()afterhasValue()):HARSIValue | undefinedopen: numberhigh: numberlow: numberclose: number
- Sub-indicators:
.open,.high,.low,.close, usable as inputs to other indicators. - Warm-up: First value on candle
period + 1.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new HARSI(candles, 14);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
manager.debugLog('HA-RSI close', value.close, value.close > value.open ? 'green' : 'red');
});
KDJ
KDJ oscillator: raw stochastic dK, then K and D smoothed with a 1/3 weight, and J = 3K − 2D.
new KDJ(candleIndicator: ICandleIndicator, extremePeriod: number)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
extremePeriod |
number |
required | High/low look-back (9 classically). |
- Input: The candle series.
- Value (
getValue()afterhasValue()):KDJValueK: numberD: numberJ: numberdK: number
- Warm-up: Values from the first candle (K and D start at 50); allow
extremePeriodcandles before trusting them.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new KDJ(candles, 9);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
manager.debugLog('K', value.K, 'D', value.D, 'J', value.J);
});
MACD
Moving average convergence divergence: macd = fast MA − slow MA, signal = MA(macd), histogram = macd − signal.
new MACD(sourceIndicator: Indicator<number | undefined>, maType: 'sma' | 'ema', fastPeriod: number, slowPeriod: number, signalMaType: 'sma' | 'ema', signalPeriod: number)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<number | undefined> |
required | The number stream (usually closes). |
maType |
'sma' | 'ema' |
required | Average for the fast and slow lines: 'ema' or 'sma'. |
fastPeriod |
number |
required | Fast average length (12 classically). |
slowPeriod |
number |
required | Slow average length (26). |
signalMaType |
'sma' | 'ema' |
required | Average for the signal line: 'ema' or 'sma'. |
signalPeriod |
number |
required | Signal average length (9). |
- Input: A number stream.
- Value (
getValue()afterhasValue()):MACDValuemacd: number | undefinedsignal: number | undefinedhistogram: number | undefined
- Warm-up:
macdafterslowPeriodcandles;signalandhistogramafterslowPeriod + signalPeriod − 1candles (34 with 12/26/9).hasValue()is true from the first candle: guard each field. - All six arguments are required; there are no defaults.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new MACD(candles.close, 'ema', 12, 26, 'ema', 9);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value.macd === undefined || value.signal === undefined || value.histogram === undefined) return;
manager.debugLog('MACD', value.macd, 'signal', value.signal, 'histogram', value.histogram);
});
Momentum
Momentum: value − value length candles ago.
new Momentum(sourceIndicator: Indicator<number | undefined>, length: number = 10)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<number | undefined> |
required | The number stream. |
length |
number |
10 |
How many candles back to compare. |
- Input: A number stream.
- Value (
getValue()afterhasValue()):number | undefined - Warm-up:
undefinedfor the firstlengthcandles.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Momentum(candles.close, 10);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
manager.debugLog('momentum', value);
});
ROC
Rate of change, in percent: 100 × (value − value length ago) / value length ago.
new ROC(sourceIndicator: Indicator<number | undefined>, length: number = 9)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<number | undefined> |
required | The number stream. |
length |
number |
9 |
How many candles back to compare. |
- Input: A number stream.
- Value (
getValue()afterhasValue()):number | undefined - Warm-up:
undefinedfor the firstlengthcandles.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new ROC(candles.close, 9);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
manager.debugLog('ROC %', value);
});
RSI
Relative strength index (Wilder): 100 − 100 / (1 + average gain / average loss) over period, between 0 and 100.
new RSI(sourceIndicator: Indicator<number>, period: number)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<number> |
required | The number stream (usually closes). |
period |
number |
required | The averaging length (14 classically). |
- Input: A number stream whose values are never
undefined(candles.close, aPartialIndicator). It does not accept the output of another averaging indicator, which isnumber | undefined. - Value (
getValue()afterhasValue()):number | undefined - Warm-up:
undefinedfor the firstperiodcandles; the first value arrives on candleperiod + 1.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new RSI(candles.close, 14);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
if (value < 30) manager.debugLog('oversold', value);
});
RSILambda
A function (price) => rsi that returns the RSI the current candle would have if it closed at price. Use it to find the price that would push RSI through a level.
new RSILambda(sourceIndicator: Indicator<number>, period: number)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<number> |
required | The number stream (usually closes). |
period |
number |
required | The RSI length. |
- Input: A number stream whose values are never
undefined. - Value (
getValue()afterhasValue()):(value: number) => number | undefined - Warm-up: The function returns
undefinedduring the firstperiodcandles.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new RSILambda(candles.close, 14);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
const ifDown2pct = value(0.98 * (candles.getValue()?.close ?? 0));
if (ifDown2pct === undefined) return;
manager.debugLog('RSI if the next close is 2% lower', ifDown2pct);
});
SqueezeMomentum
Squeeze momentum (LazyBear): detects Bollinger bands inside Keltner channels (squeezeState: 'on') and measures momentum with a linear regression of the price against its range midpoint.
new SqueezeMomentum(candleIndicator: ICandleIndicator, length: number = 20, mult: number = 2.0, lengthKC: number = 20, multKC: number = 1.5, useTrueRange: boolean = true)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
length |
number |
20 |
Bollinger length. |
mult |
number |
2.0 |
Bollinger multiplier (see note). |
lengthKC |
number |
20 |
Keltner length. |
multKC |
number |
1.5 |
Keltner multiplier. |
useTrueRange |
boolean |
true |
Use true range (true) or high − low (false) for the Keltner width. |
- Input: The candle series.
- Value (
getValue()afterhasValue()):SqueezeMomentumValuemomentum: number | undefinedsqueezeState: SqueezeState | undefinedmomentumColor: MomentumColor | undefined
SqueezeStateis'on' | 'off' | 'none'.MomentumColoris'lime' | 'green' | 'red' | 'maroon'.- Warm-up: Fields are
undefineduntil the Bollinger, Keltner and regression windows are full (aboutlength + lengthKCcandles). - Faithful to the original script, the Bollinger width uses
multKCrather thanmult.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new SqueezeMomentum(candles);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value.momentum === undefined || value.squeezeState === undefined) return;
manager.debugLog('squeeze', value.squeezeState, 'momentum', value.momentum, value.momentumColor);
});
Stochastic
Stochastic oscillator: stochastic = 100 × (close − lowest low) / (highest high − lowest low) over extremePeriod, k = its kPeriod SMA, d = the dPeriod SMA of k.
new Stochastic(sourceIndicator: Indicator<Candle | number | undefined>, extremePeriod: number, kPeriod: number, dPeriod: number)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<Candle | number | undefined> |
required | Candles, or a number stream. |
extremePeriod |
number |
required | High/low look-back (14 classically). |
kPeriod |
number |
required | %K smoothing (3 for the slow stochastic, 1 for the fast one). |
dPeriod |
number |
required | %D smoothing (3). |
- Input: The candle series (uses high/low/close), or a number stream (then the same stream serves as high, low and close).
- Value (
getValue()afterhasValue()):StochasticOutputstochastic: number | undefinedk: number | undefinedd: number | undefined
- Warm-up:
stochasticfrom the first candle;kafterkPeriodcandles;dafterkPeriod + dPeriod − 1candles. The range uses a partial window during the firstextremePeriodcandles.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Stochastic(candles, 14, 3, 3);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value.k === undefined || value.d === undefined) return;
manager.debugLog('%K', value.k, '%D', value.d);
});
TRIX
TRIX: the one-candle change of a triple EMA of the log price, times 10,000.
new TRIX(sourceIndicator: Indicator<number | undefined>, period: number = 18)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<number | undefined> |
required | The number stream. |
period |
number |
18 |
Length of each of the three EMAs. |
- Input: A number stream.
- Value (
getValue()afterhasValue()):number | undefined - Warm-up:
undefineduntil the triple EMA has two values (about3 × period − 1candles).
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new TRIX(candles.close, 18);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
manager.debugLog('TRIX', value);
});
WilliamsPercentR
Williams %R: 100 × (close − highest high) / (highest high − lowest low) over length, between −100 and 0.
new WilliamsPercentR(sourceIndicator: Indicator<Candle>, length: number = 14)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<Candle> |
required | The candle series. |
length |
number |
14 |
The look-back length. |
- Input: The candle series.
- Value (
getValue()afterhasValue()):number | undefined - Warm-up:
undefineduntillengthcandles have been seen.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new WilliamsPercentR(candles, 14);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
manager.debugLog('%R', value);
});