Skip to main content

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

  1. 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
  2. Data Access

    • The candle object in onNewBar 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
  3. 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
  4. 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