πŸš€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.

Picture 1

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.)

Picture 2

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!)

Picture 3

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