Custom Indicators
Overview
PatternFoundry includes a powerful custom indicator system that lets you write your own technical indicators using JavaScript. Custom indicators can be displayed as overlays on the price chart or as separate oscillator panels below the chart.
The indicator engine provides access to all candle data and a library of helper functions for common calculations. You write a function that receives candle data and returns computed values - PatternFoundry handles the rendering.
Opening the Editor
To open the custom indicator editor in the Simulator:
- Click the { } button in the indicator toolbar (top of chart)
- The code editor panel opens on the right side of the chart
- Write your indicator code in the editor
- Click Apply (or press Ctrl+Enter) to render the indicator
Writing Indicator Code
Your indicator code is a JavaScript function body that has access to the following data arrays:
| Variable | Type | Description |
|---|---|---|
candles | Array | Full array of candle objects: {time, open, high, low, close, volume} |
close | Array | Array of {time, value} pairs for closing prices |
open | Array | Array of {time, value} pairs for opening prices |
high | Array | Array of {time, value} pairs for high prices |
low | Array | Array of {time, value} pairs for low prices |
volume | Array | Array of {time, value} pairs for volume |
Your code must return a result in one of the supported formats (see Return Formats below).
Helper Functions
The following helper functions are available in the indicator environment:
sma(candles, period) Simple Moving Average
Computes the arithmetic mean of closing prices over the specified period.
// Returns: [{time, value}, ...] const ma20 = sma(candles, 20); // Each value = (sum of last 20 closes) / 20
ema(candles, period) Exponential Moving Average
Computes an exponentially weighted moving average. More responsive to recent price changes than SMA. Uses smoothing factor k = 2 / (period + 1).
// Returns: [{time, value}, ...] const fast = ema(candles, 9); const slow = ema(candles, 21);
rsi(candles, period) Relative Strength Index
Computes the RSI oscillator (0–100). Measures the ratio of average gains to average losses over the period. Values above 70 indicate overbought; below 30 indicate oversold.
// Returns: [{time, value}, ...] where value is 0-100 const rsiData = rsi(candles, 14);
atr(candles, period) Average True Range
Computes the average true range - a volatility measure. True range is the greatest of: current high minus low, absolute value of high minus previous close, absolute value of low minus previous close.
// Returns: [{time, value}, ...] const atrData = atr(candles, 14);
stdev(candles, period) Standard Deviation
Computes the rolling standard deviation of closing prices over the period. Useful for Bollinger Bands and volatility analysis.
// Returns: [{time, value}, ...] const sd = stdev(candles, 20);
highest(candles, period) Highest Value
Returns the highest closing price over the lookback period at each point. Useful for Donchian channels and breakout detection.
// Returns: [{time, value}, ...] const hh = highest(candles, 20);
lowest(candles, period) Lowest Value
Returns the lowest closing price over the lookback period at each point.
// Returns: [{time, value}, ...] const ll = lowest(candles, 20);
crossover(a, b) Cross Above Detection
Returns an array of {time, value} where value is 1 when array a crosses above array b, and 0 otherwise. Both arrays must have {time, value} format.
const fast = ema(candles, 9); const slow = ema(candles, 21); const bullCross = crossover(fast, slow); // bullCross[i].value === 1 when fast crosses above slow
crossunder(a, b) Cross Below Detection
Returns an array of {time, value} where value is 1 when array a crosses below array b, and 0 otherwise.
const bearCross = crossunder(fast, slow);
align(a, b) Timestamp Alignment
Aligns two arrays by their timestamps, returning only points where both arrays have data. Useful when combining indicators with different start points (due to different lookback periods).
// Returns: [{time, a: valueA, b: valueB}, ...] const aligned = align(fast, slow); // aligned[i] = {time: ..., a: fast_value, b: slow_value}
Return Formats
Your indicator must return data in one of these formats:
Single Line
An array of {time, value} objects. Rendered as a single line on the chart.
// Simple SMA overlay return sma(candles, 20);
Colored Histogram
An array of {time, value, color} objects. Rendered as a histogram with per-bar coloring.
// Momentum histogram with color const result = candles.map((c, i) => ({ time: c.time, value: c.close - c.open, color: c.close > c.open ? '#4caf82' : '#e05c5c' })); return result;
Multi-Line
An object with a lines array, where each line has its own data, label, color, style, and width.
return { lines: [ { data: upperBand, label: 'Upper', color: '#f97316', style: 'solid', width: 1 }, { data: middle, label: 'Mid', color: '#888', style: 'dashed', width: 1 }, { data: lowerBand, label: 'Lower', color: '#f97316', style: 'solid', width: 1 } ] };
Available line styles: 'solid', 'dashed', 'dotted'. Width is in pixels (1–4).
Display Types
Set the display type using the dropdown in the editor toolbar (next to the indicator name). Choose:
- Overlay - Renders on the main price chart (use for indicators in the price range, like moving averages or Bollinger Bands)
- Oscillator - Renders in a separate panel below the chart (use for bounded indicators like RSI, or difference-based indicators like MACD histogram)
The display type is NOT set in your code - it's selected from the dropdown before clicking Apply.
Examples
EMA Crossover Oscillator
Shows the difference between fast and slow EMA as a colored histogram:
const fast = ema(candles, 9); const slow = ema(candles, 21); const aligned = align(fast, slow); const hist = aligned.map(p => ({ time: p.time, value: p.a - p.b, color: p.a > p.b ? '#4caf82' : '#e05c5c' })); return hist;
Bollinger Bands (Multi-Line)
Classic Bollinger Bands with upper, middle, and lower bands:
const period = 20; const mult = 2; const mid = sma(candles, period); const sd = stdev(candles, period); const aligned = align(mid, sd); const upper = aligned.map(p => ({ time: p.time, value: p.a + mult * p.b })); const lower = aligned.map(p => ({ time: p.time, value: p.a - mult * p.b })); const middle = aligned.map(p => ({ time: p.time, value: p.a })); return { type: 'overlay', lines: [ { data: upper, label: 'BB Upper', color: '#f97316', style: 'solid', width: 1 }, { data: middle, label: 'BB Mid', color: '#666', style: 'dashed', width: 1 }, { data: lower, label: 'BB Lower', color: '#f97316', style: 'solid', width: 1 } ] };
RSI with Overbought/Oversold Coloring
RSI histogram that changes color at extreme levels:
const rsiData = rsi(candles, 14); const colored = rsiData.map(p => ({ time: p.time, value: p.value, color: p.value > 70 ? '#e05c5c' : p.value < 30 ? '#4caf82' : '#888' })); return colored;
Custom Momentum Indicator
Rate of change smoothed with an EMA, displayed as a zero-line oscillator:
const period = 10; const smoothing = 3; // Calculate rate of change const roc = []; for (let i = period; i < candles.length; i++) { roc.push({ time: candles[i].time, open: candles[i].open, high: candles[i].high, low: candles[i].low, volume: candles[i].volume, close: ((candles[i].close - candles[i - period].close) / candles[i - period].close) * 100 }); } // Smooth with EMA const smoothed = ema(roc, smoothing); const result = smoothed.map(p => ({ time: p.time, value: p.value, color: p.value > 0 ? '#4caf82' : '#e05c5c' })); return result;
Saving & Loading Indicators
To save a custom indicator for reuse:
- Write and test your indicator in the editor
- Click the Save button (floppy disk icon) in the editor toolbar
- Enter a name for your indicator (e.g., "My Bollinger Bands", "Momentum Oscillator")
- The indicator is saved to your account
To load a saved indicator:
- Click the indicator dropdown in the chart toolbar
- Your saved indicators appear under the "Custom" section
- Click to add it to the chart - it renders immediately
The indicator legend (top-left of chart or oscillator panel) shows all active indicators. For each indicator you can:
- Eye icon - Toggle visibility (hide/show without removing)
- Gear icon - Edit parameters (period, colors) without opening the full editor
- × button - Remove the indicator from the chart
PatternFoundry