Creating Custom Indicators
To create a custom indicator, extend the BaseIndicator
class. Here's a detailed example:
class MyCustomIndicator extends BaseIndicator {
constructor(id, params) {
super(id, params);
// Validate and store any parameters
if (!params.period || params.period <= 0) {
throw new Error('Invalid period parameter');
}
//store state for each token separately
this.tokenState = new Map();
}
/**
* Called for each new price bar/candle.
* - candle: Single row of price/volume data
* - barIndex: Current bar index (optional, can be used for initialization)
*/
onNewBar(candle, barIndex) {
const token = candle.token_address;
// Get or initialize state for this token
let state = this.tokenState.get(token);
if (!state) {
state = {
values: [],
sum: 0
};
this.tokenState.set(token, state);
}
// Update the indicator's value
const price = candle.close; // or any other field
state.values.push(price);
state.sum += price;
// Maintain rolling window if needed
if (state.values.length > this.params.period) {
state.sum -= state.values.shift();
}
}
/**
* Return the indicator's value for a given token
* - token: The token address to get value for
* - barIndex: Optional bar index (rarely used)
* Returns: number | undefined (use NaN for "not yet ready")
*/
getValue(token, barIndex) {
const state = this.tokenState.get(token);
if (!state || state.values.length < this.params.period) {
return NaN; // Not enough data yet
}
return state.sum / this.params.period;
}
/**
* Optional: Add custom methods to get additional values
* Similar to how MACD has getSignal() and getHistogram()
*/
getCustomValue(token) {
const state = this.tokenState.get(token);
if (!state) return NaN;
return someCalculation(state);
}
}
// Register your custom indicator
registerIndicator(
"MyCustom", // Name for this type of indicator
MyCustomIndicator, // Your class (not "BUILTIN")
{ period: 20 }, // Parameters
"CUSTOM_20" // Optional: custom ID for this instance
);
Key Points for Custom Indicators
-
State Management
- Always use a Map to store state per token
- Initialize state when first seeing a token
- Consider using TypeScript-like interfaces for state objects
-
Data Access
- The
candle
object inonNewBar
contains a single row of data - All fields (
open
,high
,low
, etc.) are single values, not arrays - Always access the token via
candle.token_address
- The
-
Value Calculation
- Return
NaN
when not enough data is available - Consider edge cases (division by zero, initialization, etc.)
- Add custom methods if you need to return multiple values
- Return
-
Best Practices
- Validate parameters in constructor
- Use clear variable names
- Comment complex calculations
- Consider memory usage with rolling windows
- Clean up old data if tokens become inactive