Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
public MyFlag!: TOptValue_bool;
public Init(): void {
// Create the parameter
this.MyFlag = this.api.createTOptValue_bool(defaultValue);
// Register the parameter
this.api.RegOption("MyFlag", TOptionType.BOOLEAN, this.MyFlag);
}export default class CustomIndicator extends IndicatorImplementation {
public IsEnabled!: TOptValue_bool;
public Init(): void {
this.IsEnabled = this.api.createTOptValue_bool(true);
this.api.RegOption("IsEnabled", TOptionType.BOOLEAN, this.IsEnabled);
}
public Calculate(index: number): void {
if (!this.IsEnabled.value) {
return;
}
// Perform calculations only if enabled
}
}CreateIndexBufferWithArgs(
index: number,
aLabel: string,
drawStyle: TDrawStyle,
style: TPenStyle,
width: number,
color: string
): TIndexBuffer// Create a buffer for a moving average with display properties
const maBuffer = this.api.CreateIndexBufferWithArgs(
0, // Index
"Moving Average", // Label
TDrawStyle.LINE, // Draw as a line
TPenStyle.SOLID, // Solid line
2, // Width of 2 pixels
"#0000ff" // Blue color
);
// Calculate and store values in the buffer
for (let i = period; i < this.api.Bars(); i++) {
maBuffer[i] = calculateMA(i, period);
}// Get the number of already calculated bars
const counted = this.api.Counted_bars()
// Use it to optimize calculations
const total = this.api.Bars()
const limit = counted > 0 ? total - counted : total - 1
// Only calculate for new bars
for (let i = limit; i >= 0; i--) {
// Perform indicator calculations for bar at index i
}A quick introduction: what they do, why traders use them, and how they can help you spot market trends and signals.
Get your tools ready — install Cursor IDE and prepare your project so you’re set to start building indicators.
This page shows you how to set up your environment for writing custom indicators in FTO. Think of it as laying out your pencils before sketching.

npm installnpm run buildcannot be loaded because running scripts is disabled on this system.
For more information, see about_Execution_Policies at
https:/go.microsoft.com/fwlink/?LinkID=135170. At line:1 char:1This guide shows you how to take your freshly built indicator and bring it into Forex Tester Online.
Get to know the Cursor AI panel — the control room for your indicator magic. We’ll guide you through its modes, buttons, and little tricks to speed up your workflow (and keep things fun).






Teach Cursor your custom rules so it knows how to handle indicators smarter and faster.
In this step, we’ll give Cursor its “textbook” — the official FTO indicator documentation.
Ready to see your hard work come alive? This tutorial shows you how to open your indicator project and upload it into Forex Tester Online so you can test it in action.










Not sure where to start? No worries — we’ve prepared a few ready-made indicator examples for you. Think of them as training wheels: you can try them out are indicator examples you can download








npm installnpm run buildcannot be loaded because running scripts is disabled on this system.
For more information, see about_Execution_Policies at
https:/go.microsoft.com/fwlink/?LinkID=135170. At line:1 char:1This tutorial will guide you through the process of creating a new indicator using Cursor IDE.
Learn how to create and run a simple Moving Average indicator in FTO — perfect for getting started with custom indicators.
Some quick tips to get the most out of Cursor and your docs
This page will guide you how to install Cursor IDE and how to upload your custom indicator to FTO.
import { IndicatorImplementation, TDrawStyle, TPenStyle, TOutputWindow, TIndexBuffer } from "forex-tester-custom-indicator-api";
export default class OBVIndicator extends IndicatorImplementation {
// Declare the buffer as a class property
public obvBuffer!: TIndexBuffer;
Init(): void {
this.api.RecalculateMeAlways();
// Set indicator name
this.api.IndicatorShortName("On Balance Volume (OBV)");
// Configure to display in separate window since OBV is an oscillator
this.api.SetOutputWindow(TOutputWindow.SEPARATE_WINDOW);
// Register the number of buffers we'll use
this.api.IndicatorBuffers(1);
// Create and initialize the OBV buffer
this.obvBuffer = this.api.CreateIndexBuffer();
// Bind buffer to index 0
this.api.SetIndexBuffer(0, this.obvBuffer);
// Configure buffer appearance
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#0000FF"); // Blue line
this.api.SetIndexLabel(0, "OBV");
}
Calculate(index: number): void {
// If this is the first bar (rightmost/newest), initialize OBV
if (index === this.api.Bars() - 1) {
this.obvBuffer.setValue(index, this.api.Volume(index));
return;
}
// Get current and previous close prices
const currentClose = this.api.Close(index);
const previousClose = this.api.Close(index + 1);
// Get current volume
const volume = this.api.Volume(index);
// Get previous OBV value
const previousOBV = this.obvBuffer.getValue(index + 1);
let currentOBV;
// Calculate OBV based on price movement
if (currentClose > previousClose) {
// If price increased, add volume
currentOBV = previousOBV + volume;
} else if (currentClose < previousClose) {
// If price decreased, subtract volume
currentOBV = previousOBV - volume;
} else {
// If price unchanged, OBV remains the same
currentOBV = previousOBV;
}
// Set the calculated OBV value
this.obvBuffer.setValue(index, currentOBV);
}
}import {
IndicatorImplementation,
TDrawStyle,
TPenStyle,
TOutputWindow,
TIndexBuffer,
TOptionType,
TOptValue_number
} from "forex-tester-custom-indicator-api";
export default class OBVIndicator extends IndicatorImplementation {
// Declare the buffer as a class property
public obvBuffer!: TIndexBuffer;
// Declare price type parameter
public priceType!: TOptValue_number;
Init(): void {
this.api.RecalculateMeAlways();
// Set indicator name
this.api.IndicatorShortName("On Balance Volume (OBV)");
// Configure to display in separate window since OBV is an oscillator
this.api.SetOutputWindow(TOutputWindow.SEPARATE_WINDOW);
// Initialize price type parameter
this.priceType = this.api.createTOptValue_number(0); // Default to Close price
// Register price type parameter as an enum
this.api.RegOption(
"Price Type",
TOptionType.ENUM_TYPE,
this.priceType
);
// Add price type options
this.api.AddOptionValue("Price Type", "Close");
this.api.AddOptionValue("Price Type", "Open");
this.api.AddOptionValue("Price Type", "High");
this.api.AddOptionValue("Price Type", "Low");
this.api.AddOptionValue("Price Type", "Median ((H+L)/2)");
this.api.AddOptionValue("Price Type", "Typical ((H+L+C)/3)");
// Register the number of buffers we'll use
this.api.IndicatorBuffers(1);
// Create and initialize the OBV buffer
this.obvBuffer = this.api.CreateIndexBuffer();
// Bind buffer to index 0
this.api.SetIndexBuffer(0, this.obvBuffer);
// Configure buffer appearance
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#0000FF"); // Blue line
this.api.SetIndexLabel(0, "OBV");
}
private getPrice(index: number): number {
switch (this.priceType.value) {
case 0: // Close
return this.api.Close(index);
case 1: // Open
return this.api.Open(index);
case 2: // High
return this.api.High(index);
case 3: // Low
return this.api.Low(index);
case 4: // Median
return (this.api.High(index) + this.api.Low(index)) / 2;
case 5: // Typical
return (this.api.High(index) + this.api.Low(index) + this.api.Close(index)) / 3;
default:
return this.api.Close(index); // Fallback to Close
}
}
Calculate(index: number): void {
// If this is the first bar (rightmost/newest), initialize OBV
if (index === this.api.Bars() - 1) {
this.obvBuffer.setValue(index, this.api.Volume(index));
return;
}
// Get current and previous prices using selected price type
const currentPrice = this.getPrice(index);
const previousPrice = this.getPrice(index + 1);
// Get current volume
const volume = this.api.Volume(index);
// Get previous OBV value
const previousOBV = this.obvBuffer.getValue(index + 1);
let currentOBV;
// Calculate OBV based on price movement
if (currentPrice > previousPrice) {
// If price increased, add volume
currentOBV = previousOBV + volume;
} else if (currentPrice < previousPrice) {
// If price decreased, subtract volume
currentOBV = previousOBV - volume;
} else {
// If price unchanged, OBV remains the same
currentOBV = previousOBV;
}
// Set the calculated OBV value
this.obvBuffer.setValue(index, currentOBV);
}
}








These are indicator examples you can download


















cannot be loaded because running scripts is disabled on this system.
For more information, see about_Execution_Policies at
https:/go.microsoft.com/fwlink/?LinkID=135170. At line:1 char:1import { IndicatorImplementation, TDrawStyle, TPenStyle, TOutputWindow, TIndexBuffer } from "forex-tester-custom-indicator-api";
export default class OBVIndicator extends IndicatorImplementation {
// Declare the buffer as a class property
public obvBuffer!: TIndexBuffer;
Init(): void {
this.api.RecalculateMeAlways();
// Set indicator name
this.api.IndicatorShortName("On Balance Volume (OBV)");
// Configure to display in separate window since OBV is an oscillator
this.api.SetOutputWindow(TOutputWindow.SEPARATE_WINDOW);
// Register the number of buffers we'll use
this.api.IndicatorBuffers(1);
// Create and initialize the OBV buffer
this.obvBuffer = this.api.CreateIndexBuffer();
// Bind buffer to index 0
this.api.SetIndexBuffer(0, this.obvBuffer);
// Configure buffer appearance
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#0000FF"); // Blue line
this.api.SetIndexLabel(0, "OBV");
}
Calculate(index: number): void {
// If this is the first bar (rightmost/newest), initialize OBV
if (index === this.api.Bars() - 1) {
this.obvBuffer.setValue(index, this.api.Volume(index));
return;
}
// Get current and previous close prices
const currentClose = this.api.Close(index);
const previousClose = this.api.Close(index + 1);
// Get current volume
const volume = this.api.Volume(index);
// Get previous OBV value
const previousOBV = this.obvBuffer.getValue(index + 1);
let currentOBV;
// Calculate OBV based on price movement
if (currentClose > previousClose) {
// If price increased, add volume
currentOBV = previousOBV + volume;
} else if (currentClose < previousClose) {
// If price decreased, subtract volume
currentOBV = previousOBV - volume;
} else {
// If price unchanged, OBV remains the same
currentOBV = previousOBV;
}
// Set the calculated OBV value
this.obvBuffer.setValue(index, currentOBV);
}
}import {
IndicatorImplementation,
TDrawStyle,
TPenStyle,
TOutputWindow,
TIndexBuffer,
TOptionType,
TOptValue_number
} from "forex-tester-custom-indicator-api";
export default class OBVIndicator extends IndicatorImplementation {
// Declare the buffer as a class property
public obvBuffer!: TIndexBuffer;
// Declare price type parameter
public priceType!: TOptValue_number;
Init(): void {
this.api.RecalculateMeAlways();
// Set indicator name
this.api.IndicatorShortName("On Balance Volume (OBV)");
// Configure to display in separate window since OBV is an oscillator
this.api.SetOutputWindow(TOutputWindow.SEPARATE_WINDOW);
// Initialize price type parameter
this.priceType = this.api.createTOptValue_number(0); // Default to Close price
// Register price type parameter as an enum
this.api.RegOption(
"Price Type",
TOptionType.ENUM_TYPE,
this.priceType
);
// Add price type options
this.api.AddOptionValue("Price Type", "Close");
this.api.AddOptionValue("Price Type", "Open");
this.api.AddOptionValue("Price Type", "High");
this.api.AddOptionValue("Price Type", "Low");
this.api.AddOptionValue("Price Type", "Median ((H+L)/2)");
this.api.AddOptionValue("Price Type", "Typical ((H+L+C)/3)");
// Register the number of buffers we'll use
this.api.IndicatorBuffers(1);
// Create and initialize the OBV buffer
this.obvBuffer = this.api.CreateIndexBuffer();
// Bind buffer to index 0
this.api.SetIndexBuffer(0, this.obvBuffer);
// Configure buffer appearance
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#0000FF"); // Blue line
this.api.SetIndexLabel(0, "OBV");
}
private getPrice(index: number): number {
switch (this.priceType.value) {
case 0: // Close
return this.api.Close(index);
case 1: // Open
return this.api.Open(index);
case 2: // High
return this.api.High(index);
case 3: // Low
return this.api.Low(index);
case 4: // Median
return (this.api.High(index) + this.api.Low(index)) / 2;
case 5: // Typical
return (this.api.High(index) + this.api.Low(index) + this.api.Close(index)) / 3;
default:
return this.api.Close(index); // Fallback to Close
}
}
Calculate(index: number): void {
// If this is the first bar (rightmost/newest), initialize OBV
if (index === this.api.Bars() - 1) {
this.obvBuffer.setValue(index, this.api.Volume(index));
return;
}
// Get current and previous prices using selected price type
const currentPrice = this.getPrice(index);
const previousPrice = this.getPrice(index + 1);
// Get current volume
const volume = this.api.Volume(index);
// Get previous OBV value
const previousOBV = this.obvBuffer.getValue(index + 1);
let currentOBV;
// Calculate OBV based on price movement
if (currentPrice > previousPrice) {
// If price increased, add volume
currentOBV = previousOBV + volume;
} else if (currentPrice < previousPrice) {
// If price decreased, subtract volume
currentOBV = previousOBV - volume;
} else {
// If price unchanged, OBV remains the same
currentOBV = previousOBV;
}
// Set the calculated OBV value
this.obvBuffer.setValue(index, currentOBV);
}
}import { IndicatorImplementation } from "forex-tester-custom-indicator-api";
export default class MovingAverage extends IndicatorImplementation {
// indicator logic
}export default class MovingAverage extends IndicatorImplementation {
// Declaring class-level fields
public Period!: TOptValue_number;
public Shift!: TOptValue_number;
public MAtype!: TOptValue_number;
public ApplyToPrice!: TOptValue_number;
public VShift!: TOptValue_number;
Init(): void {
// Create parameters using factory method
this.Period = this.api.createTOptValue_number(8);
this.Shift = this.api.createTOptValue_number(0);
this.MAtype = this.api.createTOptValue_number(E_MAType.SMA);
this.ApplyToPrice = this.api.createTOptValue_number(TPriceType.CLOSE);
this.VShift = this.api.createTOptValue_number(0);
...existing code...
}
public Init(): void {
...existing code...
// Register parameter this.Period so it's shown in the indicator settings
this.api.RegOption(
'Period',
TOptionType.INTEGER,
this.Period
);
// Setting the maximum avalable range that can be used for Period value
this.api.SetOptionRange(
'Period',
1,
Number.MAX_SAFE_INTEGER
);
// Register parameter this.Shift so it's shown in the indicator settings
this.api.RegOption(
'Shift',
TOptionType.INTEGER,
this.Shift
);
// Register parameter this.VShift so it's its shown in the indicator settings
this.api.RegOption(
'VShift',
TOptionType.INTEGER,
this.VShift
);
// Register the MA type so it has a drowdown in the indicator settings
this.api.RegMATypeOption(
this.MAtype,
'MAtype'
);
// Register the price type so it has a dropdown in the indicator settings.
this.api.RegApplyToPriceOption(
this.ApplyToPrice,
'ApplyToPrice'
);
...existing code...
}public SSMA!: TIndexBuffer
private SMA!: TIndexBufferthis.SMA = this.api.CreateIndexBuffer();
this.SSMA = this.api.CreateIndexBuffer();this.api.IndicatorBuffers(1);
this.api.SetIndexBuffer(0, this.SSMA);this.api.SetIndexLabel(0, "MA");
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#FF0000");
this.api.SetIndexDrawBegin(0, this.Period.value - 1 + this.Shift.value);public Calculate(index: number): void {
// check if the index is in the valid range
if (index + this.Period.value >= this.api.Bars()) {
return
}
// calculate the SMA value
const calculatedSMA = this.api.GetMA(
index,
0,
this.Period.value,
this.MAtype.value,
this.ApplyToPrice.value,
// here we get the value of the previous bar
this.SMA.getValue(index + 1)
)
this.SMA.setValue(index, calculatedSMA)
// set the value which is going to be displayed on the chart
this.SSMA.setValue(index, calculatedSMA + this.VShift.value * this.api.Point())
}public OnParamsChange(): void {
this.api.SetBufferShift(0, this.Shift.value)
}


cannot be loaded because running scripts is disabled on this system.
For more information, see about_Execution_Policies at
https:/go.microsoft.com/fwlink/?LinkID=135170. At line:1 char:1




index = 1 → the previous bar, and so on.SMA and SSMA are how your indicator shows up visually on the chart.
public Calculate(index: number): void {
// Skip if not enough bars to calculate moving average
if (index + this.Period.value >= this.api.Bars()) {
return
}
// Get the calculated value of the Moving Average
const calculatedSMA = this.api.GetMA(
index,
0, // Shift (usually 0)
this.Period.value, // Period for MA
this.MAtype.value, // Type of MA (SMA, EMA, etc.)
this.ApplyToPrice.value, // Price type (Close, Open, etc.)
this.SMA.getValue(index + 1) // Previous value for smoothing (optional)
)
// Save the value to the SMA buffer
this.SMA.setValue(index, calculatedSMA)
// Save a shifted version to another buffer
this.SSMA.setValue(index, calculatedSMA + this.VShift.value * this.api.Point())
}this.SSMA.setValue(index, calculatedSMA + this.VShift.value * this.api.Point())import { IndicatorImplementation } from "forex-tester-custom-indicator-api";
export default class MovingAverage extends IndicatorImplementation {
// indicator logic
}export default class MovingAverage extends IndicatorImplementation {
// Declaring class-level fields
public Period!: TOptValue_number;
public Shift!: TOptValue_number;
public MAtype!: TOptValue_number;
public ApplyToPrice!: TOptValue_number;
public VShift!: TOptValue_number;
Init(): void {
// Create parameters using factory method
this.Period = this.api.createTOptValue_number(8);
this.Shift = this.api.createTOptValue_number(0);
this.MAtype = this.api.createTOptValue_number(E_MAType.SMA);
this.ApplyToPrice = this.api.createTOptValue_number(TPriceType.CLOSE);
this.VShift = this.api.createTOptValue_number(0);
...existing code...
}
public Init(): void {
...existing code...
// Register parameter this.Period so it's shown in the indicator settings
this.api.RegOption(
'Period',
TOptionType.INTEGER,
this.Period
);
// Setting the maximum avalable range that can be used for Period value
this.api.SetOptionRange(
'Period',
1,
Number.MAX_SAFE_INTEGER
);
// Register parameter this.Shift so it's shown in the indicator settings
this.api.RegOption(
'Shift',
TOptionType.INTEGER,
this.Shift
);
// Register parameter this.VShift so it's its shown in the indicator settings
this.api.RegOption(
'VShift',
TOptionType.INTEGER,
this.VShift
);
// Register the MA type so it has a drowdown in the indicator settings
this.api.RegMATypeOption(
this.MAtype,
'MAtype'
);
// Register the price type so it has a dropdown in the indicator settings.
this.api.RegApplyToPriceOption(
this.ApplyToPrice,
'ApplyToPrice'
);
...existing code...
}public SSMA!: TIndexBuffer
private SMA!: TIndexBufferthis.SMA = this.api.CreateIndexBuffer();
this.SSMA = this.api.CreateIndexBuffer();this.api.IndicatorBuffers(1);
this.api.SetIndexBuffer(0, this.SSMA);this.api.SetIndexLabel(0, "MA");
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#FF0000");
this.api.SetIndexDrawBegin(0, this.Period.value - 1 + this.Shift.value);public Calculate(index: number): void {
// check if the index is in the valid range
if (index + this.Period.value >= this.api.Bars()) {
return
}
// calculate the SMA value
const calculatedSMA = this.api.GetMA(
index,
0,
this.Period.value,
this.MAtype.value,
this.ApplyToPrice.value,
// here we get the value of the previous bar
this.SMA.getValue(index + 1)
)
this.SMA.setValue(index, calculatedSMA)
// set the value which is going to be displayed on the chart
this.SSMA.setValue(index, calculatedSMA + this.VShift.value * this.api.Point())
}public OnParamsChange(): void {
this.api.SetBufferShift(0, this.Shift.value)
}import { IndicatorImplementation } from "forex-tester-custom-indicator-api";
export default class IndicatorName extends IndicatorImplementation {
// parameters
public Init(): void {
// initialization logic
}
public Calculate(index: number): void {
// calculation logic
}
public OnParamsChange(): void {
// logic after parameters change
}
public Done(): void {
// logic after finishing the calculation
}
public OnHide(): void {
// logic after hiding the indicator
}
public OnShow(): void {
// logic after showing the indicator
}
}public Done(): void {
// logic after finishing the calculation
}public Done(): void {
// Draw a horizontal line based on final SMA value
const lastIndex = 0
const finalValue = this.SMA.getValue(lastIndex)
// custom method CreateHorizontalLine
this.api.CreateHorizontalLine("FinalSMA", finalValue, "red")
}OnParamsChangeOnParamsChangepublic Init(): void {
// initialization logic here
}// Declare as class-level field
public period!: TOptValue_number;
public showLine!: TOptValue_bool;
public mode!: TOptValue_str;
public Init(): void {
// Create parameters with factory methods
this.period = this.api.createTOptValue_number(14);
this.showLine = this.api.createTOptValue_bool(true);
this.mode = this.api.createTOptValue_str("Simple");
// Register parameters (required for UI visibility)
this.api.RegOption("Period", TOptionType.INTEGER, this.period);
this.api.RegOption("Show Line", TOptionType.BOOLEAN, this.showLine);
this.api.RegOption("Mode", TOptionType.STRING, this.mode);
// Optional: Set parameter constraints
this.api.SetOptionRange("Period", 1, 200);
this.api.SetOptionDigits("Period", 0);
}// Declare as class-level field
public mainBuffer!: TIndexBuffer;
public Init(): void {
// 1. Register total number of buffers (MUST be called first)
this.api.IndicatorBuffers(1);
// 2. Create the buffer
this.mainBuffer = this.api.CreateIndexBuffer();
// 3. Bind buffer to index
this.api.SetIndexBuffer(0, this.mainBuffer);
// 4. Configure buffer appearance
this.api.SetIndexLabel(0, "Main Line");
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, '#FF0000');
// 5. Optional: Set drawing start point
this.api.SetIndexDrawBegin(0, 10);
}public Init(): void {
// DON'T DO THIS - heavy calculations
for (let i = 0; i < 1000; i++) {
let value = this.api.Close(i) * 2; // ❌ Wrong!
this.mainBuffer.setValue(i, value); // ❌ Wrong!
}
}public Init(): void {
// ✅ Correct - only setup and configuration
this.api.IndicatorShortName("My Indicator");
this.api.IndicatorBuffers(1);
this.mainBuffer = this.api.CreateIndexBuffer();
// ... other setup code
}
public Calculate(index: number): void {
// ✅ Correct - calculations go here
let value = this.api.Close(index) * 2;
this.mainBuffer.setValue(index, value);
}export default class MovingAverage extends IndicatorImplementation {
// Parameters - declared as class-level fields
public Period!: TOptValue_number;
public ShowLine!: TOptValue_bool;
// Buffers - declared as class-level fields
public MA!: TIndexBuffer;
public Init(): void {
// 1. Create and register parameters
this.Period = this.api.createTOptValue_number(14);
this.ShowLine = this.api.createTOptValue_bool(true);
this.api.RegOption("Period", TOptionType.INTEGER, this.Period);
this.api.RegOption("Show Line", TOptionType.BOOLEAN, this.ShowLine);
this.api.SetOptionRange("Period", 1, 9999);
// 2. Set indicator properties
this.api.IndicatorShortName("Moving Average");
this.api.SetOutputWindow(TOutputWindow.CHART_WINDOW);
this.api.RecalculateMeAlways(); // Recommended
// 3. Create and configure buffers
this.api.IndicatorBuffers(1);
this.MA = this.api.CreateIndexBuffer();
this.api.SetIndexBuffer(0, this.MA);
this.api.SetIndexLabel(0, "Moving Average");
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#FF0000");
}
public Calculate(index: number): void {
// Actual calculations happen here, not in Init()
const periodValue = this.Period.value; // Access parameter value
// ... calculation logic
}
}SetIndexDrawBegin()Init())Init()public OnHide(): void {
// logic after hiding the indicator
}public OnHide(): void {
// Custom method to remove a label created when the indicator was shown
this.DeleteObject("InfoLabel")
}public OnParamsChange(): void {
// custom logic after parameter change
}public OnParamsChange(): void {
// DON'T DO THIS - heavy calculations
for (let i = 0; i < 1000; i++) {
let value = this.api.Close(i) * this.period.value; // ❌ Wrong!
this.mainBuffer.setValue(i, value); // ❌ Wrong!
}
// DON'T DO THIS - creating new parameters
this.newParam = this.api.createTOptValue_number(10); // ❌ Wrong!
this.api.RegOption("New Param", TOptionType.INTEGER, this.newParam); // ❌ Wrong!
}public OnParamsChange(): void {
// ✅ Correct - lightweight parameter-dependent logic
this.internalMultiplier = this.period.value * 2;
// ✅ Correct - update buffer styling based on parameters
if (this.showLine.value) {
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, this.lineColor.value);
} else {
this.api.SetIndexVisibility(0, false);
}
// ✅ Correct - reset internal state
this.calculationCounter = 0;
}export default class CustomIndicator extends IndicatorImplementation {
public period!: TOptValue_number;
public lineColor!: TOptValue_str;
public showLine!: TOptValue_bool;
public mainBuffer!: TIndexBuffer;
public OnParamsChange(): void {
// Update line color when user changes it
this.api.SetIndexStyle(
0,
TDrawStyle.LINE,
TPenStyle.SOLID,
1,
this.lineColor.value
);
// Show/hide line based on boolean parameter
this.api.SetIndexVisibility(0, this.showLine.value);
// Adjust drawing start based on period
this.api.SetIndexDrawBegin(0, this.period.value);
}
}export default class AdvancedIndicator extends IndicatorImplementation {
public fastPeriod!: TOptValue_number;
public slowPeriod!: TOptValue_number;
public validConfiguration: boolean = true;
public OnParamsChange(): void {
// Validate parameter relationship
if (this.fastPeriod.value >= this.slowPeriod.value) {
this.validConfiguration = false;
// Could log warning or set visual indicator
} else {
this.validConfiguration = true;
}
// Update internal calculation variables
this.periodDifference = this.slowPeriod.value - this.fastPeriod.value;
}
private periodDifference: number = 0;
}export default class LevelIndicator extends IndicatorImplementation {
public levelValue!: TOptValue_number;
public showLevel!: TOptValue_bool;
public OnParamsChange(): void {
// Remove existing level line
this.api.RemoveAllObjects();
// Create new level line if enabled
if (this.showLevel.value) {
// Create horizontal line at new level value
this.CreateLevelLine(this.levelValue.value);
}
}
private CreateLevelLine(value: number): void {
// Custom method to create chart objects
// Implementation depends on your specific needs
}
}public OnShow(): void {
// logic after showing the indicator
}public OnShow(): void {
// Custom method to re-draw label when the indicator is shown
this.CreateTextLabel("InfoLabel", 0, this.api.High(0), "SMA Active", "blue")
}export default class CustomIndicator extends IndicatorImplementation {
// Configurable parameters
public Period!: TOptValue_number;
public ShowLabels!: TOptValue_bool;
public ApplyToPrice!: TOptValue_number;
// Internal parameter (not configurable)
public internalParameter: number = 0;
public Init(): void {
// Create parameters
this.Period = this.api.createTOptValue_number(8);
this.ShowLabels = this.api.createTOptValue_bool(true);
this.ApplyToPrice = this.api.createTOptValue_number(TPriceType.CLOSE);
// Register parameters so they show up in the UI
this.api.RegOption("Period", TOptionType.INTEGER, this.Period);
this.api.RegOption("ShowLabels", TOptionType.BOOLEAN, this.ShowLabels);
this.api.RegOption("ApplyToPrice", TOptionType.INTEGER, this.ApplyToPrice);
}
}// Declare the parameter in the class fields
public MyDateTimeParameter!: TOptValue_DateTime;
public Init(): void {
// Create the parameter
this.MyDateTimeParameter = this.api.createTOptValue_DateTime(defaultValue);
// Register the parameter
this.api.RegOption("MyDateTimeParameter", TOptionType.DATE_TIME, this.MyDateTimeParameter);
}this.MyLineStyle.colorthis.MyLineStyle.stylethis.MyLineStyle.widthInit()public MyLineStyle!: TOptValue_LineStyle;
public Init(): void {
// Create the parameter
this.MyLineStyle = this.api.createTOptValue_LineStyle(isVisible, color, style, width, ignoreColor);
// Register the parameter
this.api.RegOption("MyLineStyle", TOptionType.LINE, this.MyLineStyle);
}export default class CustomIndicator extends IndicatorImplementation {
public LineStyle!: TOptValue_LineStyle;
public Init(): void {
this.LineStyle = this.api.createTOptValue_LineStyle(true, '#FF0000', TPenStyle.SOLID, 2, false);
this.api.RegOption("LineStyle", TOptionType.LINE, this.LineStyle);
}
public Calculate(index: number): void {
if (this.LineStyle.isVisible) {
const objName = "MyHorizontalLine";
// Remove existing object if it exists
if (this.api.DoesChartObjectExist(objName)) {
this.api.RemoveChartObject(objName);
}
// Create horizontal line object
this.api.CreateChartObject(objName, TObjectType.H_LINE, 0, undefined, this.api.Close(index));
// Apply line style properties
this.api.SetObjectProperty(objName, ObjProp.OBJPROP_COLOR, this.LineStyle.color);
this.api.SetObjectProperty(objName, ObjProp.OBJPROP_STYLE, this.LineStyle.style);
this.api.SetObjectProperty(objName, ObjProp.OBJPROP_WIDTH, this.LineStyle.width);
}
}
}// Declare the parameter in the class fields
public MyDateParameter!: TOptValue_DateOnly;
public Init(): void {
// Create the parameter
this.MyDateParameter = this.api.createTOptValue_DateOnly(defaultValue);
// Register the parameter
this.api.RegOption("MyDateParameter", TOptionType.DATE_ONLY, this.MyDateParameter);
}export default class DateRangeIndicator extends IndicatorImplementation {
public StartDate!: TOptValue_DateOnly
public EndDate!: TOptValue_DateOnly
public Init(): void {
// Create the parameters
const startDate = this.api.createFTODate('2024-01-01')
const endDate = this.api.createFTODate('2024-12-31'')
this.StartDate = this.api.createTOptValue_DateOnly(startDate)
this.EndDate = this.api.createTOptValue_DateOnly(endDate)
// Register the parameters
this.api.RegOption('StartDate', TOptionType.DATE_ONLY, this.StartDate)
this.api.RegOption('EndDate', TOptionType.DATE_ONLY, this.EndDate)
}
}// Declare the parameter in the class fields
public MyParameter!: TOptValue_number;
public Init(): void {
// Create the parameter
this.MyParameter = this.api.createTOptValue_number(defaultValue);
// Register the parameter
this.api.RegOption("MyParameter", TOptionType.INTEGER, this.MyParameter);
}export default class MovingAverage extends IndicatorImplementation {
public Period!: TOptValue_number;
public Shift!: TOptValue_number;
public MAtype!: TOptValue_number;
public ApplyToPrice!: TOptValue_number;
public VShift!: TOptValue_number;
public Init(): void {
// Create the parameter
this.Period = this.api.createTOptValue_number(8);
this.Shift = this.api.createTOptValue_number(0);
this.MAtype = this.api.createTOptValue_number(E_MAType.SMA);
this.ApplyToPrice = this.api.createTOptValue_number(TPriceType.CLOSE);
this.VShift = this.api.createTOptValue_number(0);
// Register the parameter
this.api.RegOption("Period", TOptionType.INTEGER, this.Period);
this.api.RegOption("Shift", TOptionType.INTEGER, this.Shift);
this.api.RegOption("MAtype", TOptionType.INTEGER, this.MAtype);
this.api.RegOption("ApplyToPrice", TOptionType.INTEGER, this.ApplyToPrice);
this.api.RegOption("VShift", TOptionType.INTEGER, this.VShift);
}
}iTime(Symbol: string, TimeFrame: number, index: number): FTODate// Get the time of the current bar for EURUSD on H1 timeframe
const currentTime = this.api.iTime("EURUSD", 60, 0);
// Get the time from 5 bars ago
const pastTime = this.api.iTime("EURUSD", 60, 5);
// Calculate time difference between bars
const timeDiff =
this.api.iTime("EURUSD", 60, 0).toMilliseconds() -
this.api.iTime("EURUSD", 60, 1).toMilliseconds();
// Check if bar is from today
const now = this.api.createFTODate(Date.now());
const barTime = this.api.iTime("EURUSD", 60, 0);
const isToday =
barTime.getUTCDate() === now.getUTCDate() &&
barTime.getUTCMonth() === now.getUTCMonth() &&
barTime.getUTCFullYear() === now.getUTCFullYear();
// Get bar times for the last 3 bars
const barTimes = [];
for (let i = 0; i < 3; i++) {
barTimes.push(this.api.iTime("EURUSD", 60, i));
}public SSMA!: TIndexBuffer;this.SSMA = this.api.CreateIndexBuffer();this.api.IndicatorBuffers(1);this.api.SetIndexBuffer(0, this.SSMA);this.api.SetIndexLabel(0, "SSMA"); // Label shown in the legend
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#FF0000"); // Style
this.api.SetIndexDrawBegin(0, this.Period.value - 1 + this.Shift.value); // Starting barimport { TIndexBuffer } from "forex-tester-custom-indicator-api";
export default class MovingAverage extends IndicatorImplementation {
// Declare parameters as class fields
public Period!: TOptValue_number;
public Shift!: TOptValue_number;
public SSMA!: TIndexBuffer;
public Init(): void {
// Create parameters
this.Period = this.api.createTOptValue_number(8);
this.Shift = this.api.createTOptValue_number(0);
// Create and configure the buffer
this.SSMA = this.api.CreateIndexBuffer();
this.api.IndicatorBuffers(1);
this.api.SetIndexBuffer(0, this.SSMA);
this.api.SetIndexLabel(0, "SSMA");
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#FF0000");
this.api.SetIndexDrawBegin(0, this.Period.value - 1 + this.Shift.value);
}
}Volume(shift: number): number// Get current bar's volume
const currentVolume = this.api.Volume(0);
// Get previous bar's volume
const previousVolume = this.api.Volume(1);
// Calculate average volume over last 3 bars
let totalVolume = 0;
for (let i = 0; i < 3; i++) {
totalVolume += this.api.Volume(i);
}
const averageVolume = totalVolume / 3;
console.log(`Average volume over last 3 bars: ${averageVolume}`);
// Check for volume spike
if (this.api.Volume(0) > this.api.Volume(1) * 2) {
console.log("Volume spike detected on current bar");
}// Declare the parameter in the class fields
public MyText!: TOptValue_str;
public Init(): void {
// Create the parameter
this.MyText = this.api.createTOptValue_str("default text");
// Register the parameter
this.api.RegOption("MyText", TOptionType.STRING, this.MyText);
}export default class CustomIndicator extends IndicatorImplementation {
public Name!: TOptValue_str;
public Init(): void {
this.Name = this.api.createTOptValue_str("Custom Indicator");
this.api.RegOption("Name", TOptionType.STRING, this.Name);
}
}iVolume(Symbol: string, TimeFrame: number, index: number): number// Get the volume of the current bar for EURUSD on H1 timeframe
const currentVolume = this.api.iVolume("EURUSD", 60, 0);
// Get the volume from 5 bars ago
const pastVolume = this.api.iVolume("EURUSD", 60, 5);
// Calculate the total volume over the last 3 bars
const totalVolume =
this.api.iVolume("EURUSD", 60, 0) +
this.api.iVolume("EURUSD", 60, 1) +
this.api.iVolume("EURUSD", 60, 2);
// Calculate average volume over last 3 bars
const avgVolume = totalVolume / 3;
// Check if current volume is higher than previous bar
if (this.api.iVolume("EURUSD", 60, 0) > this.api.iVolume("EURUSD", 60, 1)) {
console.log("Volume is increasing");
}
// Check for volume spike (2x average)
const isVolumeSpiking = this.api.iVolume("EURUSD", 60, 0) > avgVolume * 2;iClose(Symbol: string, TimeFrame: number, index: number): number// Get the close price of the current bar for EURUSD on H1 timeframe
const currentClose = this.api.iClose("EURUSD", 60, 0);
// Get the close price from 5 bars ago
const pastClose = this.api.iClose("EURUSD", 60, 5);
// Calculate the difference between current and previous bar's close prices
const closeDiff =
this.api.iClose("EURUSD", 60, 0) - this.api.iClose("EURUSD", 60, 1);
// Check if current bar closed higher than previous bar
if (this.api.iClose("EURUSD", 60, 0) > this.api.iClose("EURUSD", 60, 1)) {
console.log("Current bar closed higher");
}
// Calculate average closing price of last 3 bars
const avgClose =
(this.api.iClose("EURUSD", 60, 0) +
this.api.iClose("EURUSD", 60, 1) +
this.api.iClose("EURUSD", 60, 2)) /
3;iOpen(Symbol: string, TimeFrame: number, index: number): number// Get the open price of the current bar for EURUSD on H1 timeframe
const currentOpen = this.api.iOpen("EURUSD", 60, 0);
// Get the open price from 5 bars ago
const pastOpen = this.api.iOpen("EURUSD", 60, 5);
// Calculate the difference between current and previous bar's open prices
const openDiff =
this.api.iOpen("EURUSD", 60, 0) - this.api.iOpen("EURUSD", 60, 1);
// Check if current bar opened higher than previous bar
if (this.api.iOpen("EURUSD", 60, 0) > this.api.iOpen("EURUSD", 60, 1)) {
console.log("Current bar opened higher");
}// Declare the parameter in the class fields
public MyTimeParameter!: TOptValue_TimeOnly;
public Init(): void {
// Create the parameter
this.MyTimeParameter = this.api.createTOptValue_TimeOnly(defaultTimeValue);
// Register the parameter
this.api.RegOption("MyTimeParameter", TOptionType.TIME_ONLY, this.MyTimeParameter);
}export default class SessionIndicator extends IndicatorImplementation {
public SessionStart!: TOptValue_TimeOnly
public SessionEnd!: TOptValue_TimeOnly
public Init(): void {
// Create the parameters
this.SessionStart = this.api.createTOptValue_TimeOnly(TimeValue['09:00'])
this.SessionEnd = this.api.createTOptValue_TimeOnly(TimeValue['17:00'])
// Register the parameters
this.api.RegOption('SessionStart', TOptionType.TIME_ONLY, this.SessionStart)
this.api.RegOption('SessionEnd', TOptionType.TIME_ONLY, this.SessionEnd)
}
}iHighest(symbol: string, timeFrame: number, type: number, count: number, index: number): number// Find highest high price in last 10 bars
const highestIndex = this.api.iHighest("EURUSD", 60, 1, 10, 0);
if (highestIndex !== -1) {
const highestPrice = this.api.iHigh("EURUSD", 60, highestIndex);
console.log(`Highest price: ${highestPrice} at index ${highestIndex}`);
}
// Find highest close in last 20 bars
const highestCloseIndex = this.api.iHighest("EURUSD", 60, 3, 20, 0);
// Find highest volume in last 5 bars
const highestVolumeIndex = this.api.iHighest("EURUSD", 60, 4, 5, 0);
// Check if current bar is highest in last 50 bars
const isNewHigh = this.api.iHighest("EURUSD", 60, 1, 50, 0) === 0;
// Find highest high starting from a specific bar
const startIndex = 10;
const lookback = 5;
const highIndex = this.api.iHighest("EURUSD", 60, 1, lookback, startIndex);
// Get highest price values for different types
const types = [0, 1, 2, 3]; // OPEN, HIGH, LOW, CLOSE
const highestValues = types.map((type) => {
const idx = this.api.iHighest("EURUSD", 60, type, 10, 0);
return idx !== -1 ? this.api.iHigh("EURUSD", 60, idx) : null;
});iHigh(Symbol: string, TimeFrame: number, index: number): number// Get the high price of the current bar for EURUSD on H1 timeframe
const currentHigh = this.api.iHigh("EURUSD", 60, 0);
// Get the high price from 5 bars ago
const pastHigh = this.api.iHigh("EURUSD", 60, 5);
// Calculate the highest price over the last 3 bars
const highest = Math.max(
this.api.iHigh("EURUSD", 60, 0),
this.api.iHigh("EURUSD", 60, 1),
this.api.iHigh("EURUSD", 60, 2)
);
// Check if current bar's high is a new local high
if (this.api.iHigh("EURUSD", 60, 0) > this.api.iHigh("EURUSD", 60, 1)) {
console.log("New local high formed");
}
// Calculate the average high price of last 3 bars
const avgHigh =
(this.api.iHigh("EURUSD", 60, 0) +
this.api.iHigh("EURUSD", 60, 1) +
this.api.iHigh("EURUSD", 60, 2)) /
3;iLow(Symbol: string, TimeFrame: number, index: number): number// Get the low price of the current bar for EURUSD on H1 timeframe
const currentLow = this.api.iLow("EURUSD", 60, 0);
// Get the low price from 5 bars ago
const pastLow = this.api.iLow("EURUSD", 60, 5);
// Calculate the lowest price over the last 3 bars
const lowest = Math.min(
this.api.iLow("EURUSD", 60, 0),
this.api.iLow("EURUSD", 60, 1),
this.api.iLow("EURUSD", 60, 2)
);
// Check if current bar's low is a new local low
if (this.api.iLow("EURUSD", 60, 0) < this.api.iLow("EURUSD", 60, 1)) {
console.log("New local low formed");
}
// Calculate the average low price of last 3 bars
const avgLow =
(this.api.iLow("EURUSD", 60, 0) +
this.api.iLow("EURUSD", 60, 1) +
this.api.iLow("EURUSD", 60, 2)) /
3;
// Calculate bar range
const barRange =
this.api.iHigh("EURUSD", 60, 0) - this.api.iLow("EURUSD", 60, 0);iBarShift(symbol: string, timeframe: number, time: FTODate, exact: boolean): number// Find bar index for a specific time
const searchTime = this.api.createFTODate("2023-01-01T10:00:00Z");
const barIndex = this.api.iBarShift("EURUSD", 60, searchTime, true);
// Check if specific time exists in history
if (this.api.iBarShift("EURUSD", 60, searchTime, true) !== -1) {
console.log("Bar found for the specified time");
}
// Find nearest bar before a time
const approxIndex = this.api.iBarShift("EURUSD", 60, searchTime, false);
// Get price at specific historical time
const historicalTime = this.api.createFTODate("2023-06-01T14:30:00Z");
const index = this.api.iBarShift("EURUSD", 60, historicalTime, false);
if (index !== -1) {
const price = this.api.iClose("EURUSD", 60, index);
console.log(`Price at ${historicalTime}: ${price}`);
}
// Find bar index for current time
const now = this.api.createFTODate(Date.now());
const currentIndex = this.api.iBarShift("EURUSD", 60, now, false);iBars(Symbol: string, TimeFrame: number): number// Get total number of bars for EURUSD on H1 timeframe
const totalBars = this.api.iBars("EURUSD", 60);
// Check if enough historical data is available
const requiredBars = 100;
if (this.api.iBars("EURUSD", 60) >= requiredBars) {
console.log("Sufficient historical data available");
}
// Calculate average over all available bars
let sum = 0;
const bars = this.api.iBars("EURUSD", 60);
for (let i = 0; i < bars; i++) {
sum += this.api.iClose("EURUSD", 60, i);
}
const average = sum / bars;
// Find the oldest available bar's time
const oldestBarIndex = this.api.iBars("EURUSD", 60) - 1;
const oldestTime = this.api.iTime("EURUSD", 60, oldestBarIndex);
// Check data availability across timeframes
const m1Bars = this.api.iBars("EURUSD", 1);
const h1Bars = this.api.iBars("EURUSD", 60);
const d1Bars = this.api.iBars("EURUSD", 1440);Close(shift: number): number// Get current bar's closing price
const currentClose = this.api.Close(0);
// Get previous bar's closing price
const previousClose = this.api.Close(1);
// Calculate price change
const priceChange = this.api.Close(0) - this.api.Close(1);
console.log(`Price changed by ${priceChange} points`);
// Get closing prices for last 3 bars
for (let i = 0; i < 3; i++) {
const closePrice = this.api.Close(i);
console.log(`Bar -${i} close price: ${closePrice}`);
}Time(shift: number, timeZoneMode?: TimeZoneMode): FTODate// Get current bar's time in project timezone
const currentTime = this.api.Time(0);
console.log(`Current bar time: ${currentTime.toString()}`);
// Get current bar's time in UTC
const currentTimeUTC = this.api.Time(0, TimeZoneMode.UTC);
console.log(`Current bar UTC time: ${currentTimeUTC.toString()}`);
// Get previous bar's time
const previousTime = this.api.Time(1);
// Calculate time difference between bars
const timeDiff = currentTime.getTime() - previousTime.getTime();
console.log(`Time between bars: ${timeDiff} milliseconds`);
// Get opening times for last 3 bars
for (let i = 0; i < 3; i++) {
const time = this.api.Time(i);
console.log(`Bar -${i} opened at: ${time.toString()}`);
}number representing the total count of available bars.Bars(): numberHigh(shift: number): number// Get current bar's high price
const currentHigh = this.api.High(0);
// Get previous bar's high price
const previousHigh = this.api.High(1);
// Find highest price over last 3 bars
let highestPrice = this.api.High(0);
for (let i = 1; i < 3; i++) {
const high = this.api.High(i);
if (high > highestPrice) {
highestPrice = high;
}
}
console.log(`Highest price in last 3 bars: ${highestPrice}`);
// Check if current bar made new high
if (this.api.High(0) > this.api.High(1)) {
console.log("New high formed on current bar");
}Open(shift: number): number// Get current bar's opening price
const currentOpen = this.api.Open(0);
// Get previous bar's opening price
const previousOpen = this.api.Open(1);
// Compare current and previous opening prices
const openDiff = this.api.Open(0) - this.api.Open(1);
console.log(
`Price opened ${openDiff > 0 ? "higher" : "lower"} than previous bar`
);
// Get opening prices for last 3 bars
for (let i = 0; i < 3; i++) {
const openPrice = this.api.Open(i);
console.log(`Bar -${i} open price: ${openPrice}`);
}// Get total number of bars
const totalBars = this.api.Bars();
console.log(`Total available bars: ${totalBars}`);
// Check if enough history for analysis
const requiredBars = 20;
if (this.api.Bars() >= requiredBars) {
// Perform analysis requiring 20 bars of history
}
// Process last 10 bars (if available)
const barsToProcess = Math.min(10, this.api.Bars());
for (let i = 0; i < barsToProcess; i++) {
const close = this.api.Close(i);
console.log(`Bar -${i} close price: ${close}`);
}
// Calculate valid shift range
const maxShift = this.api.Bars() - 1;
console.log(`Valid shift range: 0 to ${maxShift}`);Low(shift: number): number// Get current bar's low price
const currentLow = this.api.Low(0);
// Get previous bar's low price
const previousLow = this.api.Low(1);
// Find lowest price over last 3 bars
let lowestPrice = this.api.Low(0);
for (let i = 1; i < 3; i++) {
const low = this.api.Low(i);
if (low < lowestPrice) {
lowestPrice = low;
}
}
console.log(`Lowest price in last 3 bars: ${lowestPrice}`);
// Check if current bar made new low
if (this.api.Low(0) < this.api.Low(1)) {
console.log("New low formed on current bar");
}RemoveAllObjectsByPrefix(prefix: string, isStatic?: boolean, window?: number): void// Remove all objects with names starting with "MyIndicator_"
this.api.RemoveAllObjectsByPrefix("MyIndicator_");
// Remove all static objects with prefix "Label"
this.api.RemoveAllObjectsByPrefix("Label", true);
// Remove all objects with prefix from MainChart
this.api.RemoveAllObjectsByPrefix("Temp_", false, 0);RemoveAllObjects(objType: TObjectType, isStatic?: boolean, window?: number): voidDoesChartObjectExist(uniqueObjectName: string, isStatic: boolean = false): booleanСreateChartObject(
name: string,
objType: TObjectType,
window: number,
ftoDate1: FTODate,
price1: number,
ftoDate2?: FTODate,
price2?: number,
ftoDate3?: FTODate,
price3?: number,
isStatic?: boolean
): boolean// Remove all trend lines
this.api.RemoveAllObjects(TObjectType.TREND_LINE);
// Remove all static text labels
this.api.RemoveAllObjects(TObjectType.TEXT, true);
// Clean up all drawing objects
const objectTypes = [
TObjectType.TREND_LINE,
TObjectType.RECTANGLE,
TObjectType.TRIANGLE,
TObjectType.TEXT,
];
for (const type of objectTypes) {
this.api.RemoveAllObjects(type);
}
// Remove objects and log count
const beforeCount = this.api.GetObjectCount();
this.api.RemoveAllObjects(TObjectType.RECTANGLE);
const afterCount = this.api.GetObjectCount();
console.log(`Removed ${beforeCount - afterCount} rectangle objects`);// Check if object exists before using it
if (this.api.DoesChartObjectExist("MyTrendLine")) {
// Object exists, safe to use
this.api.SetObjectProperty("MyTrendLine", ObjProp.OBJPROP_COLOR, 0xff0000);
} else {
console.log("Object not found");
}
// Check static object
const staticExists = this.api.DoesChartObjectExist("MyStaticLabel", true);
console.log(`Static object exists: ${staticExists}`);
// Create object only if it doesn't exist
const objectName = "UniqueObject";
if (!this.api.DoesChartObjectExist(objectName)) {
this.api.СreateChartObject(
objectName,
TObjectType.TEXT,
0,
this.api.createFTODate(Date.now()),
1.2345
);
}
// Remove object if it exists
if (this.api.DoesChartObjectExist("OldObject")) {
this.api.RemoveChartObject("OldObject");
}// Create text object
this.api.CreateChartObject('MyLabel', TObjectType.TEXT, 0, this.api.Time(0), this.api.Close(0))
// Set text content and styling
this.api.SetObjectProperty('MyLabel', ObjProp.OBJPROP_TEXT, 'Support Level')
this.api.SetObjectProperty('MyLabel', ObjProp.OBJPROP_FONTNAME, 'Arial')
this.api.SetObjectProperty('MyLabel', ObjProp.OBJPROP_FONTSIZE, 12)
this.api.SetObjectProperty('MyLabel', ObjProp.OBJPROP_COLOR, '#0000FF')
this.api.SetObjectProperty('MyLabel', ObjProp.OBJPROP_ANCHOR_POINT, AnchorPoint.CENTER)// Create rectangle
this.api.CreateChartObject(
'MyRectangle',
TObjectType.RECTANGLE,
0,
this.api.Time(10),
this.api.Close(10),
this.api.Time(0),
this.api.Close(0)
)
// Set rectangle styling
this.api.SetObjectProperty('MyRectangle', ObjProp.OBJPROP_COLOR, '#00FF00')
this.api.SetObjectProperty('MyRectangle', ObjProp.OBJPROP_FILLCOLOR, '#00FF0020')
this.api.SetObjectProperty('MyRectangle', ObjProp.OBJPROP_FILLINSIDE, true)
this.api.SetObjectProperty('MyRectangle', ObjProp.OBJPROP_WIDTH, 1)
this.api.SetObjectProperty('MyRectangle', ObjProp.OBJPROP_BACK, true)
this.api.SetObjectProperty('MyRectangle', ObjProp.OBJPROP_MIDDLE_LINE, true)
// Set text content and styling for rectangle
this.api.SetObjectProperty('MyRectangle', ObjProp.OBJPROP_TEXT, 'Text')
this.api.SetObjectProperty('MyRectangle', ObjProp.OBJPROP_FONTNAME, 'Arial')
this.api.SetObjectProperty('MyRectangle', ObjProp.OBJPROP_FONTSIZE, 12)// Create a text object with screen coordinates
this.api.CreateChartObject('FixedLabel', TObjectType.TEXT, 0, 0, 0)
// Enable screen coordinates mode
this.api.SetObjectProperty('FixedLabel', ObjProp.OBJPROP_SCREENCOORDS, true)
// Set fixed position relative to chart corner (top-left)
this.api.SetObjectProperty('FixedLabel', ObjProp.OBJPROP_XDISTANCE, 50) // 50 pixels from left
this.api.SetObjectProperty('FixedLabel', ObjProp.OBJPROP_YDISTANCE, 30) // 30 pixels from top
// Set fixed size
this.api.SetObjectProperty('FixedLabel', ObjProp.OBJPROP_XSIZE, 200) // 200 pixels wide
this.api.SetObjectProperty('FixedLabel', ObjProp.OBJPROP_YSIZE, 40) // 40 pixels tall
// Configure text properties
this.api.SetObjectProperty('FixedLabel', ObjProp.OBJPROP_TEXT, 'Fixed Position Label')
this.api.SetObjectProperty('FixedLabel', ObjProp.OBJPROP_FONTNAME, 'Arial')
this.api.SetObjectProperty('FixedLabel', ObjProp.OBJPROP_FONTSIZE, 14)
this.api.SetObjectProperty('FixedLabel', ObjProp.OBJPROP_COLOR, '#333333')
this.api.SetObjectProperty('FixedLabel', ObjProp.OBJPROP_ANCHOR_POINT, AnchorPoint.LEFT_TOP)GetObjectName(index: number, isStatic?: boolean, window?: number): stringGetObjectCount(isStatic?: boolean, window?: number): number// Get name of first object
const firstName = this.api.GetObjectName(0)
console.log(`First object name: ${firstName}`)
// Get name of first static object
const firstStaticName = this.api.GetObjectName(0, true)
console.log(`First static object name: ${firstStaticName}`)
// List all objects
const count = this.api.GetObjectCount()
for (let i = 0; i < count; i++) {
const name = this.api.GetObjectName(i)
const type = this.api.GetObjectType(name)
console.log(`Object ${i}: Name=${name}, Type=${type}`)
}
// List all static objects
const staticCount = this.api.GetObjectCount(true)
for (let i = 0; i < staticCount; i++) {
const name = this.api.GetObjectName(i, true)
const type = this.api.GetObjectType(name, true)
console.log(`Static object ${i}: Name=${name}, Type=${type}`)
}
// List objects from MainChart (window = 0)
const mainChartCount = this.api.GetObjectCount(false, 0)
for (let i = 0; i < mainChartCount; i++) {
const name = this.api.GetObjectName(i, false, 0)
console.log(`MainChart object ${i}: ${name}`)
}// Get count of regular objects
const regularCount = this.api.GetObjectCount();
console.log(`Regular objects: ${regularCount}`);
// Get count of static objects
const staticCount = this.api.GetObjectCount(true);
console.log(`Static objects: ${staticCount}`);
// Use counts in a loop
for (let i = 0; i < this.api.GetObjectCount(); i++) {
const objectName = this.api.GetObjectName(i);
console.log(`Object ${i}: ${objectName}`);
}
// Count objects in MainChart (window = 0)
const mainChartCount = this.api.GetObjectCount(false, 0);
// Count objects across all windows
const totalCount = this.api.GetObjectCount(false, -1);GetObjectText(name: string, isStatic: boolean = false): string// Get text from a text label
const labelText = this.api.GetObjectText('MyLabel')
console.log(`Label text: ${labelText}`)
// Get text from a static label
const staticText = this.api.GetObjectText('MyStaticLabel', true)
console.log(`Static label text: ${staticText}`)
// List all text objects with their content
const count = this.api.GetObjectCount()
for (let i = 0; i < count; i++) {
const name = this.api.GetObjectName(i)
if (this.api.GetObjectType(name) === TObjectType.TEXT) {
const text = this.api.GetObjectText(name)
console.log(`Text object ${name}: "${text}"`)
}
}
// Error handling example
try {
const text = this.api.GetObjectText('NonExistentObject')
} catch (error) {
console.log('Error getting object text:', error.message)
}GetCurrentWindowIndex(): numberSetBufferShift(bufferIndex: number, shift: number): void// Check if running in MainChart or indicator window
const windowIndex = this.api.GetCurrentWindowIndex();
if (windowIndex === 0) {
console.log("Running in MainChart");
} else {
console.log(`Running in indicator window ${windowIndex}`);
}
// Count objects in current window only
const count = this.api.GetObjectCount(false, this.api.GetCurrentWindowIndex());// Remove a regular chart object
this.api.RemoveChartObject('MyTrendLine')
// Remove a static chart object
this.api.RemoveChartObject('MyStaticLabel', true)
// Remove object after checking existence
if (this.api.DoesChartObjectExist('MyObject')) {
this.api.RemoveChartObject('MyObject')
console.log('Object removed successfully')
}
// Remove multiple related objects
const objectPrefix = 'Signal_'
for (let i = 0; i < this.api.GetObjectCount(); i++) {
const name = this.api.GetObjectName(i)
if (name.startsWith(objectPrefix)) {
this.api.RemoveChartObject(name)
}
}// Shift buffer 0 forward by 5 bars (into the future)
this.api.SetBufferShift(0, 5)
// Shift buffer 1 backward by 3 bars (into the past)
this.api.SetBufferShift(1, -3)
// Use shifting to create a predictive indicator
const predictionPeriod = 10
this.api.SetBufferShift(0, predictionPeriod)SetObjectProperty(
name: string,
index: number,
value: any,
isStatic: boolean = false
): boolean// Set object coordinates
const success1 = this.api.SetObjectProperty(
"MyTrendLine",
ObjProp.OBJPROP_TIME1,
this.api.createFTODate(1641024000000)
);
const success2 = this.api.SetObjectProperty(
"MyTrendLine",
ObjProp.OBJPROP_PRICE1,
1.2
);
// Set visual properties
this.api.SetObjectProperty("MyTrendLine", ObjProp.OBJPROP_COLOR, 0xff0000); // Red color
this.api.SetObjectProperty("MyTrendLine", ObjProp.OBJPROP_STYLE, 1); // Solid line
this.api.SetObjectProperty("MyTrendLine", ObjProp.OBJPROP_WIDTH, 2); // Line width
// Set text properties
this.api.SetObjectProperty("MyLabel", ObjProp.OBJPROP_TEXT, "New Label Text");
this.api.SetObjectProperty("MyLabel", ObjProp.OBJPROP_FONTSIZE, 12);
// Set object state
this.api.SetObjectProperty("MyTrendLine", ObjProp.OBJPROP_HIDDEN, true);GetObjectType(name: string, isStatic: boolean = false): TObjectType// Get type of a specific object
const type = this.api.GetObjectType("MyTrendLine");
console.log(`Object type: ${type}`);
// Check object type
if (this.api.GetObjectType("MyLine") === TObjectType.TREND_LINE) {
console.log("Object is a trend line");
}
// List all objects with their types
const count = this.api.GetObjectCount();
for (let i = 0; i < count; i++) {
const name = this.api.GetObjectName(i);
const type = this.api.GetObjectType(name);
console.log(`Object ${name} is of type ${type}`);
}
// Check static object type
const staticType = this.api.GetObjectType("MyStaticLine", true);
if (staticType === TObjectType.V_LINE) {
console.log("Static object is a vertical line");
}SetObjectText(
name: string,
text: string,
fontSize: number = 14,
fontName: string = Roboto Flex,
fontColor: string = '#000000',
isStatic: boolean = false
): booleanSetOutputWindow(outputWindow: TOutputWindow): void// Set basic text
const success1 = this.api.SetObjectText("MyLabel", "Hello World");
console.log(`Text set: ${success1}`);
// Set text with custom formatting
const success2 = this.api.SetObjectText(
"MyLabel",
"Custom Text",
14, // font size
"Arial",
0xff0000 // red color
);
console.log(`Formatted text set: ${success2}`);// Display indicator in the main chart window (like Moving Averages, Bollinger Bands)
this.api.SetOutputWindow(TOutputWindow.CHART_WINDOW);
// Display indicator in a separate window (like RSI, MACD, Stochastic)
this.api.SetOutputWindow(TOutputWindow.SEPARATE_WINDOW);AddLevel(value: number, style: TPenStyle, width: number, color: string, opacity: number): void// Add an overbought level at 70 (red line)
this.api.AddLevel(70, TPenStyle.SOLID, 1, "#ff0000", 1);
// Add an oversold level at 30 (green line)
this.api.AddLevel(30, TPenStyle.SOLID, 1, "#00ff00", 1);
// Add a middle level with a dashed line (gray line)
this.api.AddLevel(50, TPenStyle.DASH, 1, "#808080", 0.7);GetBufferCount(buffer: number): number// Get the count of values in buffer 0
const count = this.api.GetBufferCount(0);
// Use the count to iterate through all values in the buffer
for (let i = 0; i < count; i++) {
const value = this.someBuffer.getValue(i);
// Process the value
}
// Check if there are enough values for calculations
if (this.api.GetBufferCount(0) >= period) {
// Perform calculations that require at least 'period' values
}GetBufferMin(buffer: number, index1: number, index2: number): number// Create buffer
public someBuffer = this.api.CreateIndexBuffer();
// Assign the buffer to index 0
this.api.SetIndexBuffer(0, this.someBuffer)
// Find the minimum value in buffer 0 over the last 20 bars
const min = this.api.GetBufferMin(0, 0, 19)
// Find the minimum value in buffer 1 over a custom range
const startIndex = 10
const endIndex = 50
const minInRange = this.api.GetBufferMin(1, startIndex, endIndex)
// Use the minimum value for normalization
const range = this.api.GetBufferMax(0, 0, 19) - min
for (let i = 0; i < 20; i++) {
const normalizedValue = (this.someBuffer.getValue(i) - min) / range
this.someBuffer.setValue(i, normalizedValue)
}SetIndexStyle(
bufferIndex: number,
type: TDrawStyle,
style: TPenStyle,
width: number,
clr: string,
isVisible?: boolean
): voidiLowest(symbol: string, timeFrame: number, type: number, count: number, index: number): numberIndicatorDigits(digits: number): voidpublic Init(): void {
// Set initial style with visibility
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 2, "#0000ff", true); // Initially visible
this.api.SetIndexStyle(1, TDrawStyle.NONE, TPenStyle.SOLID, 1, "#000000", false); // Initially hidden
}public Calculate(index: number): void {
// Show different buffers based on market state
if (this.isInTrendingMarket(index)) {
this.api.SetIndexVisibility(0, true); // Trend buffer
this.api.SetIndexVisibility(1, false); // Range buffer
} else {
this.api.SetIndexVisibility(0, false); // Trend buffer
this.api.SetIndexVisibility(1, true); // Range buffer
}
}public Init(): void {
// Initial buffer setup with visibility
// Buffer 0: Always visible main line
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 2, "#0000ff", true);
// Buffer 1: Initially hidden calculation buffer
this.api.SetIndexStyle(1, TDrawStyle.NONE, TPenStyle.SOLID, 1, "#000000", false);
// Buffer 2: Initially hidden, will be shown conditionally
this.api.SetIndexStyle(2, TDrawStyle.HISTOGRAM, TPenStyle.SOLID, 3, "#00ff00", false);
}
public Calculate(index: number): void {
// Algorithmic visibility control - use SetIndexVisibility
const signalStrength = this.calculateSignalStrength(index);
if (signalStrength > 0.8) {
this.api.SetIndexVisibility(2, true); // Show strong signals only
} else {
this.api.SetIndexVisibility(2, false); // Hide weak signals
}
// DON'T do this in Calculate() or any method thats not Init():
// this.api.SetIndexStyle(2, TDrawStyle.HISTOGRAM, TPenStyle.SOLID, 3, "#00ff00", true); // ❌ Wrong!
}// Find lowest low price in last 10 bars
const lowestIndex = this.api.iLowest("EURUSD", 60, 2, 10, 0);
if (lowestIndex !== -1) {
const lowestPrice = this.api.iLow("EURUSD", 60, lowestIndex);
console.log(`Lowest price: ${lowestPrice} at index ${lowestIndex}`);
}
// Find lowest close in last 20 bars
const lowestCloseIndex = this.api.iLowest("EURUSD", 60, 3, 20, 0);
// Find lowest volume in last 5 bars
const lowestVolumeIndex = this.api.iLowest("EURUSD", 60, 4, 5, 0);
// Check if current bar is lowest in last 50 bars
const isNewLow = this.api.iLowest("EURUSD", 60, 2, 50, 0) === 0;
// Find lowest low starting from a specific bar
const startIndex = 10;
const lookback = 5;
const lowIndex = this.api.iLowest("EURUSD", 60, 2, lookback, startIndex);
// Get lowest price values for different types
const types = [0, 1, 2, 3]; // OPEN, HIGH, LOW, CLOSE
const lowestValues = types.map((type) => {
const idx = this.api.iLowest("EURUSD", 60, type, 10, 0);
return idx !== -1 ? this.api.iLow("EURUSD", 60, idx) : null;
});
// Find price channel
const highestHigh = this.api.iHigh(
"EURUSD",
60,
this.api.iHighest("EURUSD", 60, 1, 20, 0)
);
const lowestLow = this.api.iLow(
"EURUSD",
60,
this.api.iLowest("EURUSD", 60, 2, 20, 0)
);
const channelHeight = highestHigh - lowestLow;// Set indicator to display 2 decimal places
this.api.IndicatorDigits(2)
// For a price-based indicator on EURUSD (which typically has 5 decimal places)
this.api.IndicatorDigits(5)
// For an RSI indicator (values between 0-100)
this.api.IndicatorDigits(1)GetBufferInfo(index: number): TVisibleBufferInfo// Get information about buffer 0
const bufferInfo = this.api.GetBufferInfo(0);
// Log buffer properties
console.log(`Buffer Name: ${bufferInfo.name}`);
console.log(`Paint From: ${bufferInfo.paintFrom}`);
// Modify buffer visibility based on a condition
if (bufferInfo.paintFrom > 0) {
this.api.SetIndexVisibility(0, true);
} else {
this.api.SetIndexVisibility(0, false);
}GetObjectProperty(
name: string,
index: ObjProp | number,
isStatic: boolean = false
): number | string// Get object coordinates
const time1 = this.api.GetObjectProperty("MyTrendLine", ObjProp.OBJPROP_TIME1);
const price1 = this.api.GetObjectProperty(
"MyTrendLine",
ObjProp.OBJPROP_PRICE1
);
console.log(`First point: Time=${time1}, Price=${price1}`);
// Get object color
const color = this.api.GetObjectProperty("MyTrendLine", ObjProp.OBJPROP_COLOR);
console.log(`Object color: ${color}`);
// Get text content
const text = this.api.GetObjectProperty("MyLabel", ObjProp.OBJPROP_TEXT);
console.log(`Label text: ${text}`);SetIndexDrawBegin(bufferIndex: number, paintFrom: number): voidGetBufferMax(buffer: number, index1: number, index2: number): numberthis.bufferName.setValue(index, value);this.someBuffer.getValue(index);// For a 14-period moving average, don't draw the first 13 bars
this.api.SetIndexDrawBegin(0, 13);
// For a 26-period EMA, don't draw until we have enough data
this.api.SetIndexDrawBegin(0, 25);
// For MACD with 12 and 26 periods, don't draw until we have enough data for both
this.api.SetIndexDrawBegin(0, 25); // MACD line
this.api.SetIndexDrawBegin(1, 33); // Signal line (26 + 9 - 1)// Create buffer
public someBuffer = this.api.CreateIndexBuffer();
// Assign the buffer to index 0
this.api.SetIndexBuffer(0, this.someBuffer)
// Find the maximum value in buffer 0 over the last 20 bars
const max = this.api.GetBufferMax(0, 0, 19)
// Find the maximum value in buffer 1 over a custom range
const startIndex = 10
const endIndex = 50
const maxInRange = this.api.GetBufferMax(1, startIndex, endIndex)
// Use the maximum value for scaling
const scaleFactor = 100 / max
for (let i = 0; i < 20; i++) {
const scaledValue = this.someBuffer(0, i) * scaleFactor
this.someBuffer.setValue(i, scaledValue)
}// Calculate a simple moving average
let sum = 0;
for (let i = 0; i < period; i++) {
sum += this.api.Close(i);
}
const average = sum / period;
// Store the calculated value in buffer 0 at the current bar
this.someBuffer.setValue(0, average);
// Store values for multiple bars
for (let i = 0; i < this.api.Bars(); i++) {
const value = calculateIndicatorValue(i);
this.someBuffer.setValue(i, value);
}// Get the value from buffer 0 at the current bar
const currentValue = this.someBuffer.getValue(0);
// Get the value from buffer 0 at the previous bar
const previousValue = this.someBuffer.getValue(1);
// Get a value from another buffer
const signalValue = this.someBuffer.getValue(0);
// Use values in calculations
const difference = currentValue - signalValue;SetIndexVisibility(index: number, isVisible: boolean): voidexport default class TrendRangeIndicator extends IndicatorImplementation {
public trendBuffer!: TIndexBuffer;
public rangeBuffer!: TIndexBuffer;
private isInTrend: boolean = false;
public Calculate(index: number): void {
// Determine market state
this.isInTrend = this.calculateTrendState(index);
if (this.isInTrend) {
// Show trend buffer, hide range buffer
this.api.SetIndexVisibility(0, true); // Trend buffer
this.api.SetIndexVisibility(1, false); // Range buffer
this.trendBuffer.setValue(index, this.calculateTrendValue(index));
} else {
// Show range buffer, hide trend buffer
this.api.SetIndexVisibility(0, false); // Trend buffer
this.api.SetIndexVisibility(1, true); // Range buffer
this.rangeBuffer.setValue(index, this.calculateRangeValue(index));
}
}
}export default class SmartSignalIndicator extends IndicatorImplementation {
public mainBuffer!: TIndexBuffer;
public warningBuffer!: TIndexBuffer;
public signalBuffer!: TIndexBuffer;
public Calculate(index: number): void {
const mainValue = this.calculateMainValue(index);
this.mainBuffer.setValue(index, mainValue);
// Only show warning when market is volatile
const volatility = this.calculateVolatility(index);
if (volatility > 2.0) {
this.api.SetIndexVisibility(1, true); // Show warning
this.warningBuffer.setValue(index, mainValue);
} else {
this.api.SetIndexVisibility(1, false); // Hide warning
}
// Only show signals when conditions are optimal
const signalStrength = this.calculateSignalStrength(index);
if (signalStrength > 0.8 && volatility < 1.5) {
this.api.SetIndexVisibility(2, true); // Show signal
this.signalBuffer.setValue(index, this.generateSignal(index));
} else {
this.api.SetIndexVisibility(2, false); // Hide signal
}
}
}export default class MultiTimeframeIndicator extends IndicatorImplementation {
public shortTermBuffer!: TIndexBuffer;
public longTermBuffer!: TIndexBuffer;
public OnParamsChange(): void {
const currentTimeframe = this.api.Timeframe();
if (currentTimeframe <= 60) {
// 1 hour or less
// Show short-term analysis
this.api.SetIndexVisibility(0, true); // Short-term buffer
this.api.SetIndexVisibility(1, false); // Long-term buffer
} else {
// Show long-term analysis
this.api.SetIndexVisibility(0, false); // Short-term buffer
this.api.SetIndexVisibility(1, true); // Long-term buffer
}
}
}export default class QualityControlIndicator extends IndicatorImplementation {
public reliableBuffer!: TIndexBuffer;
public unreliableBuffer!: TIndexBuffer;
public Calculate(index: number): void {
const dataQuality = this.assessDataQuality(index);
const calculatedValue = this.performCalculation(index);
if (dataQuality > 0.9) {
// High quality data - show reliable buffer
this.api.SetIndexVisibility(0, true); // Reliable buffer
this.api.SetIndexVisibility(1, false); // Unreliable buffer
this.reliableBuffer.setValue(index, calculatedValue);
} else {
// Low quality data - show unreliable buffer with warning style
this.api.SetIndexVisibility(0, false); // Reliable buffer
this.api.SetIndexVisibility(1, true); // Unreliable buffer
this.unreliableBuffer.setValue(index, calculatedValue);
}
}
}SetIndexSymbol(bufferIndex: number, symbol: number, xoffs: number, yoffs: number): void// Set buffer 0 to use symbol 217 (arrow up) with no offset
this.api.SetIndexSymbol(0, 217, 0, 0);
// Set buffer 1 to use symbol 218 (arrow down) with a slight offset
this.api.SetIndexSymbol(1, 218, 2, -2);SetIndexBuffer(bufferIndex: number, buffer: TIndexBuffer): void// Specify that the indicator uses 3 buffers
this.api.IndicatorBuffers(3);
// Create buffers
const upperBuffer = this.api.CreateIndexBuffer();
const middleBuffer = this.api.CreateIndexBuffer();
const lowerBuffer = this.api.CreateIndexBuffer();
// Assign buffers to indices
this.api.SetIndexBuffer(0, upperBuffer);
this.api.SetIndexBuffer(1, middleBuffer);
this.api.SetIndexBuffer(2, lowerBuffer);
// Now the buffers can be used to store indicator valuesSetIndexLabel(bufferIndex: number, bufferName: string): void// Set labels for Bollinger Bands buffers
this.api.SetIndexLabel(0, 'Upper Band')
this.api.SetIndexLabel(1, 'Middle Band')
this.api.SetIndexLabel(2, 'Lower Band')
// Set labels for MACD buffers
this.api.SetIndexLabel(0, 'MACD Line')
this.api.SetIndexLabel(1, 'Signal Line')
this.api.SetIndexLabel(2, 'Histogram')CreateIndexBuffer(): TIndexBufferSetEmptyValue(emptyValue: number): voidIndicatorBuffers(bufferCount: number): void// Set the indicator to recalculate on every tick
this.api.RecalculateMeAlways();IndicatorBuffers()SetIndexBuffer()// ✅ Correct - declare as class-level fields
export default class MyIndicator extends IndicatorImplementation {
public mainBuffer!: TIndexBuffer;
public signalBuffer!: TIndexBuffer;
public helperBuffer!: TIndexBuffer;
public Init(): void {
this.mainBuffer = this.api.CreateIndexBuffer();
this.signalBuffer = this.api.CreateIndexBuffer();
this.helperBuffer = this.api.CreateIndexBuffer();
}
}
// ❌ Wrong - don't use api methods (any api methods, including CreateIndexBuffer) in class level fields
export default class MyIndicator extends IndicatorImplementation {
public mainBuffer = this.api.CreateIndexBuffer();
public signalBuffer = this.api.CreateIndexBuffer();
public helperBuffer = this.api.CreateIndexBuffer();
public Init(): void {}
}export default class MovingAverageIndicator extends IndicatorImplementation {
// Declare all buffers as class properties
public maBuffer!: TIndexBuffer;
public signalBuffer!: TIndexBuffer; public Init(): void {
// 1. Register total number of buffers FIRST
this.api.IndicatorBuffers(2);
// 2. Create buffer instances
this.maBuffer = this.api.CreateIndexBuffer();
this.signalBuffer = this.api.CreateIndexBuffer();
// 3. Assign buffers to indexes
this.api.SetIndexBuffer(0, this.maBuffer);
this.api.SetIndexBuffer(1, this.signalBuffer);
// 4. Configure buffer appearance
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 2, "#0000ff");
this.api.SetIndexStyle(1, TDrawStyle.LINE, TPenStyle.DASH, 1, "#ff0000");
// 5. Set buffer labels
this.api.SetIndexLabel(0, "Moving Average");
this.api.SetIndexLabel(1, "Signal Line");
} public Calculate(index: number): void {
// Use buffers to store and retrieve values
const closePrice = this.api.Close(index);
const maValue = this.calculateMA(index);
// Set buffer values
this.maBuffer.setValue(index, maValue);
this.signalBuffer.setValue(index, maValue * 1.1);
// Get buffer values (from previous bars)
const previousMA = this.maBuffer.getValue(index + 1);
}
}export default class AdvancedIndicator extends IndicatorImplementation {
// 1. Declare all buffers as class-level fields
public fastMA!: TIndexBuffer;
public slowMA!: TIndexBuffer;
public signalBuffer!: TIndexBuffer;
public helperBuffer!: TIndexBuffer;
public Init(): void {
// 2. Register total buffer count
this.api.IndicatorBuffers(4);
// 3. Create buffer instances
this.fastMA = this.api.CreateIndexBuffer();
this.slowMA = this.api.CreateIndexBuffer();
this.signalBuffer = this.api.CreateIndexBuffer();
this.helperBuffer = this.api.CreateIndexBuffer();
// 4. Assign to indexes
this.api.SetIndexBuffer(0, this.fastMA);
this.api.SetIndexBuffer(1, this.slowMA);
this.api.SetIndexBuffer(2, this.signalBuffer);
this.api.SetIndexBuffer(3, this.helperBuffer);
// 5. Configure styles
this.api.SetIndexStyle(0, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#0000ff");
this.api.SetIndexStyle(1, TDrawStyle.LINE, TPenStyle.SOLID, 1, "#ff0000");
this.api.SetIndexStyle(
2,
TDrawStyle.HISTOGRAM,
TPenStyle.SOLID,
2,
"#00ff00"
);
this.api.SetIndexStyle(3, TDrawStyle.NONE, TPenStyle.SOLID, 1, "#000000"); // Hidden
// 6. Set labels
this.api.SetIndexLabel(0, "Fast MA");
this.api.SetIndexLabel(1, "Slow MA");
this.api.SetIndexLabel(2, "Signal");
}
public Calculate(index: number): void {
// Use buffers for calculations and display
const fastValue = this.calculateFastMA(index);
const slowValue = this.calculateSlowMA(index);
this.fastMA.setValue(index, fastValue);
this.slowMA.setValue(index, slowValue);
this.helperBuffer.setValue(index, fastValue - slowValue);
// Conditional signal display
if (Math.abs(fastValue - slowValue) > 0.01) {
this.signalBuffer.setValue(index, fastValue);
}
}
}// Set empty values to zero
this.api.SetEmptyValue(0)
// Use a negative value to clearly identify missing data points
this.api.SetEmptyValue(-1)
// Common practice is to use a very large negative number
this.api.SetEmptyValue(-999999)// For a simple indicator with one line
this.api.IndicatorBuffers(1)
// For Bollinger Bands (middle, upper, lower)
this.api.IndicatorBuffers(3)
// For MACD (MACD line, signal line, histogram)
this.api.IndicatorBuffers(3)SetBackOffsetForCalculation(offset: number): voidIndicatorShortName(shortName: string): voidSetIndicatorIdKey(key: string): voidSetLevelValue(index: number, value: number): void// Include 50 additional bars in calculations
this.api.SetBackOffsetForCalculation(50)
// For indicators that need a lot of historical data
this.api.SetBackOffsetForCalculation(200)// Set a simple name
this.api.IndicatorShortName('RSI(14)')
// Include parameter values in the name
const period = 14
const price = 'Close'
this.api.IndicatorShortName(`MA(${period}, ${price})`)
// For a custom indicator with multiple parameters
this.api.IndicatorShortName(`Custom Oscillator(${fast}, ${slow}, ${signal})`)// Set a simple key
this.api.SetIndicatorIdKey('RSI_14')
// Use a more complex key with parameters
this.api.SetIndicatorIdKey(`MA_${period}_${method}_${price}`)
// Generate a unique key
this.api.SetIndicatorIdKey(`CustomIndicator_${Date.now()}`)// Change the first level (index 0) to value 75
this.api.SetLevelValue(0, 75)
// Change the second level (index 1) to value 25
this.api.SetLevelValue(1, 25)
// Dynamically adjust levels based on volatility
const volatility = this.api.iATR('EURUSD', 14, 0)
this.api.SetLevelValue(0, 50 + volatility * 2)
this.api.SetLevelValue(1, 50 - volatility * 2)SetFixedMinMaxValues(aMin: number, aMax: number): void// Set fixed scale for RSI (0-100)
this.api.SetFixedMinMaxValues(0, 100)
// Set fixed scale for Stochastic (0-100)
this.api.SetFixedMinMaxValues(0, 100)
// Set custom range for an oscillator (-50 to 50)
this.api.SetFixedMinMaxValues(-50, 50)