Custom Indicators
Custom indicators let you create your own technical analysis tools beyond the built-in indicators. You can define custom calculations and visualize them on your chart in various ways.
Quick Start
Here's the simplest way to create a custom indicator:
// Step 1: Register your custom indicator
chartApi.registerCustomIndicator({
name: 'Custom_SMA',
displayName: 'Simple Moving Average',
description: 'A custom moving average indicator',
category: 'trend',
// Define adjustable parameters
inputs: [
{
name: 'length',
type: 'number',
defaultValue: 14,
min: 1,
max: 100,
step: 1,
description: 'Number of periods to average'
}
],
// Define the calculation logic
calculate: (getRealBar, getDerivedBar, index, options) => {
const length = options.length || 14;
const currentBar = getRealBar(index);
// Not enough data yet
if (!currentBar || index < length - 1) return null;
// Calculate the average
let sum = 0;
for (let j = 0; j < length; j++) {
const bar = getRealBar(index - j);
if (bar) sum += bar.close;
}
return { value: sum / length };
},
// Define how it looks on the chart
renderLayers: [
{
type: 'line',
dataKey: 'value',
color: '#2196F3',
lineWidth: 2
}
],
// Where to display it
pane: 'main',
categoryType: 'overlay'
});
// Step 2: Add it to your chart
chartApi.addIndicator('Custom_SMA');
Understanding the Basics
What is a Custom Indicator?
A custom indicator is a formula you create to analyze price data. It consists of three parts:
- Inputs - Settings users can adjust (like period length)
- Calculate - The math that processes the data
- Render Layers - How the results appear on the chart
The Three Parts Explained
1. Inputs (Settings)
Inputs are the knobs and dials users can adjust. Each input has:
- name - Internal name used in your code
- type - What kind of value:
'number','string','boolean', or'color' - defaultValue - Starting value
- min/max - Allowed range (for numbers)
- step - How much it changes when adjusted
- description - Help text for users
Example:
inputs: [
{
name: 'period',
type: 'number',
defaultValue: 20,
min: 5,
max: 50,
step: 1,
description: 'Number of bars to calculate'
},
{
name: 'color',
type: 'color',
defaultValue: '#FF0000',
description: 'Line color'
}
]
2. Calculate Function (The Math)
This is where you define what your indicator does. The function runs once for each bar on the chart.
What you receive:
- getRealBar(index) - Get price data (open, high, low, close, volume) at any bar index
- getDerivedBar(index) - Get previous calculation results (useful for indicators that build on themselves, like EMA)
- index - Current bar number being calculated
- options - The user's current input settings
What you return:
- Return an object with your calculated values:
{ value: 123.45 } - Return
nullif you don't have enough data yet
Simple Example:
calculate: (getRealBar, getDerivedBar, index, options) => {
// Get current bar's data
const bar = getRealBar(index);
// Check if we have valid data
if (!bar) return null;
// Calculate something (example: double the close price)
const result = bar.close * 2;
// Return the result
return { value: result };
}
Advanced Example (using previous results):
calculate: (getRealBar, getDerivedBar, index, options) => {
const bar = getRealBar(index);
if (!bar) return null;
// First bar - just use the close price
if (index === 0) {
return { ema: bar.close };
}
// Get the previous EMA value
const prevResult = getDerivedBar(index - 1);
if (!prevResult) return null;
// Calculate new EMA using previous value
const multiplier = 2 / (options.length + 1);
const ema = (bar.close - prevResult.ema) * multiplier + prevResult.ema;
return { ema: ema };
}
3. Render Layers (The Visual)
Render layers control how your indicator looks on the chart. You can have multiple layers for complex indicators.
Available Layer Types:
- line - A continuous line
- area - Filled area under a line
- histogram - Vertical bars from zero line
- column - Vertical bars
- scatter - Individual dots
- candle - Candlestick representation
- bgcolor - Background color highlighting
- fill - Fill between two lines
Layer Properties:
- type - Visual style (see types above)
- dataKey - Which value from your calculate function to display
- color - Line/fill color (hex code like '#FF0000')
- lineWidth - Thickness of lines (1-5)
- opacity - Transparency (0.0 to 1.0)
- fill - Fill color (for area type)
- secondaryKey - Second data key (for fill type - fills between two lines)
- dashPattern - Dashed line pattern (for line type)
Examples:
// Simple line
renderLayers: [
{
type: 'line',
dataKey: 'value',
color: '#2196F3',
lineWidth: 2
}
]
// Multiple lines (like MACD)
renderLayers: [
{
type: 'histogram',
dataKey: 'histogram',
color: '#999999',
opacity: 0.5
},
{
type: 'line',
dataKey: 'macd',
color: '#2196F3',
lineWidth: 2
},
{
type: 'line',
dataKey: 'signal',
color: '#FF5722',
lineWidth: 2
}
]
// Fill between two lines (like Bollinger Bands)
renderLayers: [
{
type: 'fill',
dataKey: 'upper',
secondaryKey: 'lower',
color: '#2196F3',
opacity: 0.1
},
{ type: 'line', dataKey: 'upper', color: '#2196F3', lineWidth: 1 },
{ type: 'line', dataKey: 'middle', color: '#FF9800', lineWidth: 2 },
{ type: 'line', dataKey: 'lower', color: '#2196F3', lineWidth: 1 }
]
// Background coloring
renderLayers: [
{
type: 'bgcolor',
dataKey: 'highlight',
color: '#FFEB3B',
opacity: 0.2
}
]
Where to Display Your Indicator
Pane Options
- 'main' - Overlay on the main price chart (for indicators that work with price)
- 'new' - Create a separate panel below the chart (for oscillators and indicators with different scales)
Category Types
- 'overlay' - Draws on top of price candles/bars
- 'oscillator' - Independent indicator with its own scale
Examples:
// Moving average - overlays on price
{
pane: 'main',
categoryType: 'overlay'
}
// RSI - separate panel
{
pane: 'new',
categoryType: 'oscillator'
}
Managing Your Indicators
Adding to Chart
After registering, add the indicator to your chart:
chartApi.addIndicator('Custom_SMA');
With custom options:
chartApi.addIndicator('Custom_SMA', { length: 50 });
Removing from Chart
chartApi.removeIndicator(indicatorId);
Hiding/Showing
// Hide indicator (paneId is usually 0 for main, 1+ for sub-panels)
chartApi.setIndicatorVisibility(indicatorId, false, 0);
// Show indicator
chartApi.setIndicatorVisibility(indicatorId, true, 0);
Updating Settings
chartApi.updateIndicator(indicatorId, {
inputs: { length: 30 }
}, 0);
Listening for Events
Get notified when indicators are added:
chartApi.onIndicatorAdded((indicator) => {
console.log('New indicator added:', indicator.name);
});
Tips & Best Practices
1. Always Check Your Data
calculate: (getRealBar, getDerivedBar, index, options) => {
const bar = getRealBar(index);
// Always check if data exists
if (!bar) return null;
if (!bar.close) return null;
// Your calculation here...
}
2. Handle Warm-up Periods
Many indicators need historical data before they can calculate:
calculate: (getRealBar, getDerivedBar, index, options) => {
const period = options.period || 20;
// Not enough history yet
if (index < period - 1) return null;
// Now calculate...
}
3. Use Descriptive Names
// Good
{
name: 'EMA_Indicator',
displayName: 'Exponential Moving Average',
description: 'Smooths price data with more weight on recent prices'
}
// Bad
{
name: 'x',
displayName: 'Indicator'
}
4. Provide Reasonable Input Ranges
// Good - prevents user errors
{
name: 'period',
type: 'number',
defaultValue: 14,
min: 2,
max: 200,
step: 1
}
// Bad - no constraints
{
name: 'period',
type: 'number',
defaultValue: 14
}
5. Test on Different Timeframes
Make sure your indicator works on:
- 1-minute charts
- Daily charts
- Weekly charts
6. Use Colors Wisely
- Use contrasting colors for multiple lines
- Consider dark/light theme compatibility
- Standard colors: Blue (#2196F3), Red (#F44336), Green (#4CAF50), Orange (#FF9800)
Troubleshooting
Indicator Not Showing
Check:
- ✅ Did you call
addIndicator()afterregisterCustomIndicator()? - ✅ Is the indicator in the correct pane? (main vs new)
- ✅ Are you returning valid data from calculate?
- ✅ Check browser console for errors
Line Not Drawing
Check:
- ✅ Is your dataKey correct? (must match what calculate returns)
- ✅ Are you returning numbers, not strings?
- ✅ Is there enough data? (check for null returns)
Colors Look Wrong
Check:
- ✅ Use hex format: '#FF0000' (with #)
- ✅ Check opacity values (0.0 to 1.0)
- ✅ Layer order matters (first layers draw first)
Performance Issues
Tips:
- Avoid unnecessary loops in calculate
- Don't calculate more than needed
- Use
getDerivedBarfor recursive calculations instead of recalculating history
Next Steps
- Try modifying the example above with different calculations
- Experiment with different render layer types
- Adjust input parameters to see how they affect your indicator
- Create your own unique indicators!
Need Help?
- Check the browser console for error messages
- Review the example in this document
- Look at the built-in indicators for inspiration
- Test with small datasets first
Remember: Custom indicators are a powerful way to create your own analysis tools. Start simple, test often, and build up complexity gradually!