πStep 5: Start building
Alright, the warm-up is done β now itβs time to get your hands dirty (figuratively).
This is where we actually start writing code for your custom indicator.
Before we dive into coding, make sure your setup is ready to roll. Youβll need access to the FTO Indicator API to make things work smoothly.
Not there yet? No problem β check out the setup guide first, get your environment prepped, and then come back here. Itβs a quick read and will save you a ton of head-scratching later.
For this example, weβll let Cursor do some of the heavy lifting: weβll ask it to create the On Balance Volume (OBV) indicator for us. Weβll start simple β with a completely empty file β so you can see the process from scratch. Later, when youβre more comfortable, you can speed things up by using some of our pre-built indicator foundations (youβll find them in this section).
Think of this as baking your first cake from flour and sugar β once youβve got the hang of it, you can use ready-made mixes to save time.
When we asked Cursor to create the OBV indicator, we didnβt just say βdo it.β We gave it some context β included the FTO Indicator documentation and the Rules we set up earlier. In the screenshot (Picture #1), you can see exactly which parts of the documentation Cursor decided to use. Itβs like watching your AI buddy flip through the manual before giving you the best answer.

In the result, we got code below
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);
}
}
Time for a Quick Fix
The current logic looks solid so far, but thereβs a catch: itβs locked to using the Close price only. Thatβs a bit limiting, right? What if someone wants to use the High, Low, or Open price instead?
No worries β letβs get Cursor to step up its game. Weβll ask it to tweak the code so users can pick their preferred price type. (See Picture #2 to follow along.)

Cursorβs Updated Code
After we asked Cursor to improve the OBV indicator, hereβs what it came up with in its second response.
This version lets users choose the price type instead of being stuck with the Close price β nice upgrade! (See below.)
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);
}
}
A Little Upgrade Goes a Long Way
With that new parameter and internal method added, Cursor gives us a neat bonus: a dropdown menu for choosing the price type.
No more hardcoded Close price β now you can pick what you need right from the list. (See Picture #3 β thatβs your shiny new option selector!)

Wrapping Up
And thatβs it β with just two simple requests, Cursor delivered a fully working OBV indicator.
All it took was a proper setup, some clear Rules, and a bit of documentation magic to give it the right context.
Not bad for a few clicks, right? Less typing, more building.
Last updated