Algorithmic Trading Platform & FIX API Solutions

QuantXpress provides trading APIs, FIX protocol solutions, algorithmic trading software, and low latency trading systems.

Our platform supports automated trading, market data APIs, and high-frequency trading infrastructure for developers and institutions.

Trading API and FIX Protocol Solutions

Build scalable trading systems using our FIX engine, market data APIs, and execution management systems.

Algorithmic Trading Platform FIX Protocol API Market Data API

Create Your Own Custom Indicator Using QX.FinLib

Build a custom Exponential Moving Average (EMA) indicator from scratch

5 min read • Technical Article

Introduction

The QX.FinLib library provides a powerful and flexible framework for building custom technical indicators. In this article, we will walk through the process of creating your own Exponential Moving Average (EMA) indicator, which we’ll name EMAX1 from scratch.

By the end of this guide, you will understand:

Step 1: Define Your Indicator Class

Every custom indicator must inherit from IndicatorBase and be decorated with the IndicatorAttribute. This allows the QX.FinLib indicator registration manager to register and discover your indicator

Here is the basic skeleton:

[IndicatorAttribute("{29D98F27-D628-4E04-AAC1-D9690C718FB7}", "EMAX1", "Exponential Moving Average", "QXT")]
public class EMAX1: IndicatorBase {
}

Understanding the IndicatorAttribute Parameters

Parameter Example Value Purpose
GUID {29D98F27-D628-4E04-AAC1-D9690C718FB7} Uniquely identifies your indicator. Generate a new GUID for each custom indicator.
Unique Name EMAX1 Short, unique name used internally.
Description Exponential Moving Average Human-readable description shown in UI.
Owner ID QXT Identifies the owner or organization (e.g., QXT).

Important: Always generate a new, unique GUID for your indicator. Do not copy existing GUIDs.

Step 2: Add Parameters and Outputs

To make your indicator flexible, you need to expose configurable Parameters (like period length) and define Output series for the final values.

Define a Parameter
Use the [Parameter] attribute to expose user-configurable input settings:

[Parameter("LengthPeriod", Description = "EMA Length", DefaultValue = 12)]
private int _lengthPeriod = 12;

Define an Output Series
Use the [Output] attribute to store and display calculated values:

[Output("EMA", DefaultValue = double.NaN, LineColor = "DarkOrange")] 
private DoubleSeries _emaDoubleValueSeries; 

Complete Class with Properties

[IndicatorAttribute("{29D98F27-D628-4E04-AAC1-D9690C718FB7}", "EMAX1", "Exponential Moving Average", "QXT")]
public class EMAX1: IndicatorBase { [Parameter("LengthPeriod", Description = "The length of the EMA", DefaultValue = 12)]
  private int _lengthPeriod = 12;
  [Output("EMA", DefaultValue = double.NaN, LineColor = "DarkOrange")]
  private DoubleSeries _emaDoubleValueSeries;
  private double _exponentialMultiplier = 0;

}

Step 3: Implement Constructors

You must provide three constructors to ensure flexibility:

// Full constructor with parameters 
public EMAX1(TimeDataSeries timeDataSeries, int lengthPeriod)
: base(timeDataSeries)
{
  if (lengthPeriod <= 0)
  throw new ArgumentException("Invalid EMA length");
  _lengthPeriod = lengthPeriod;
  _exponentialMultiplier = 2.0 / (_lengthPeriod + 1);
  _emaDoubleValueSeries = new DoubleSeries();
}

// Constructor with default period 
public EMAX1(TimeDataSeries timeDataSeries)
: base(timeDataSeries)
{
  _exponentialMultiplier = 2.0 / (_lengthPeriod + 1);
  _emaDoubleValueSeries = new DoubleSeries();
}

// Parameterless constructor (required for serialization) 
public EMAX1()
: base(null)
{
  _exponentialMultiplier = 2.0 / (_lengthPeriod + 1);
  _emaDoubleValueSeries = new DoubleSeries();
}

Step 4: Implementation of Key override Methods

The IndicatorBase class provides virtual methods that act as the lifecycle of your calculation.

4.1 Initialize() Method
Called once during initialization. Use it to set up variables and pre-calculate constants.

protected override void Initialize() 
{ 
    // Calculate the smoothing multiplier 
    _exponentialMultiplier = 2.0 / (_lengthPeriod + 1); 
}

4.2  CalculateOnBar(int index)  Method
This is the core logic of your indicator. It is called for each bar/index in the time series and must return the indicator value at that index.

protected override double CalculateOnBar(int index) 
{ 
    double currentPrice = this.InputSeries[index]; 
    // Not enough data yet 
    if (index < _lengthPeriod - 1) 
    { 
        _emaDoubleValueSeries[index] = double.NaN; 
    } 
    // First valid EMA = SMA of first 'lengthPeriod' bars 
    else if (index == _lengthPeriod - 1) 
    { 
        _emaDoubleValueSeries[index] = this.InputSeries.Average(_lengthPeriod); 
    } 
    else 
    { 
        double previousEMA = _emaDoubleValueSeries[index - 1]; 
        // Fallback safety: if previous EMA is invalid, recalculate as SMA 
        if (!previousEMA.HasValue()) 
        { 
            _emaDoubleValueSeries[index] = this.InputSeries.Average(_lengthPeriod); 
        } 
        else 
        { 
            // Standard EMA formula: 
            // EMA = (Price - PreviousEMA) * Multiplier + PreviousEMA 
            double ema = ((currentPrice - previousEMA) * _exponentialMultiplier) + previousEMA; 
            _emaDoubleValueSeries[index] = ema; 
        } 
    } 
    return _emaDoubleValueSeries[index]; 
}

4.3  ResetIndicator()  Method
Called when the indicator needs to be recalculated from scratch (e.g., when parameters change or backtest restarts).

protected override void ResetIndicator() 
{ 
    // Reset multiplier 
    _exponentialMultiplier = 2.0 / (_lengthPeriod + 1); 
    // Clear the output series 
    _emaDoubleValueSeries = new DoubleSeries(); 
}

4.4 (Optional)  GetIndicatorCategoryType()  Method
Override this to categorize your indicator in the UI:

public override IndicatorCategoryType GetIndicatorCategoryType() 
{ 
    return IndicatorCategoryType.General; 
}

Step 5: Complete EMAX1 Indicator Code

Here is the  full, working code  for your custom EMA indicator:

using System; 
using QX.FinLib.Data; 
namespace QX.FinLib.Data.TI 
{ 
    [IndicatorAttribute("{29D98F27-D628-4E04-AAC1-D9690C718FB7}", "EMAX1", "Exponential Moving Average", "QXT")] 
    public class EMAX1 : IndicatorBase 
    { 
        [Parameter("LengthPeriod", Description = "The length of the EMA", DefaultValue = 12)] 
        private int _lengthPeriod = 12;          
        [Output("EMA", DefaultValue = double.NaN, LineColor = "DarkOrange")] 
        private DoubleSeries _emaDoubleValueSeries; 
        private double _exponentialMultiplier = 0; 
        // Full constructor 
        public EMAX1(TimeDataSeries timeDataSeries, int lengthPeriod) 
            : base(timeDataSeries) 
        { 
            if (lengthPeriod <= 0) 
                throw new ArgumentException("Invalid EMA length"); 
            _lengthPeriod = lengthPeriod; 
            _exponentialMultiplier = 2.0 / (_lengthPeriod + 1); 
            _emaDoubleValueSeries = new DoubleSeries(); 
        } 
        // Default period constructor 
        public EMAX1(TimeDataSeries timeDataSeries) 
            : base(timeDataSeries) 
        { 
            _exponentialMultiplier = 2.0 / (_lengthPeriod + 1); 
            _emaDoubleValueSeries = new DoubleSeries(); 
        } 
        // Parameterless constructor 
        public EMAX1() 
            : base(null) 
        { 
            _exponentialMultiplier = 2.0 / (_lengthPeriod + 1); 
            _emaDoubleValueSeries = new DoubleSeries(); 
        } 
        public override IndicatorCategoryType GetIndicatorCategoryType() 
        { 
            return IndicatorCategoryType.General; 
        } 
        protected override void Initialize() 
        { 
            _exponentialMultiplier = 2.0 / (_lengthPeriod + 1); 
        } 
        protected override double CalculateOnBar(int index) 
        { 
            double currentPrice = this.InputSeries[index]; 
            if (index < _lengthPeriod - 1) 
            { 
                _emaDoubleValueSeries[index] = double.NaN; 
            } 
            else if (index == _lengthPeriod - 1) 
            { 
                _emaDoubleValueSeries[index] = this.InputSeries.Average(_lengthPeriod); 
            } 
            else 
            { 
                double previousEMA = _emaDoubleValueSeries[index - 1]; 
                if (!previousEMA.HasValue()) 
                { 
                    _emaDoubleValueSeries[index] = this.InputSeries.Average(_lengthPeriod); 
                } 
                else 
                { 
                    double ema = ((currentPrice - previousEMA) * _exponentialMultiplier) + previousEMA; 
                    _emaDoubleValueSeries[index] = ema; 
                } 
            } 
            return _emaDoubleValueSeries[index]; 
        } 
        protected override void ResetIndicator() 
        { 
            _exponentialMultiplier = 2.0 / (_lengthPeriod + 1); 
            _emaDoubleValueSeries = new DoubleSeries(); 
        } 
    } 
} 

Once Indicator is available as in form of DLL or a code class in your project. You can use Indicator as following

TimeDataSeries timeDataSeries5Min = TimeDataSeries.GetMinutesBarCompression(500);
CSVOHLCLoader csvOHLCLoader = new CSVOHLCLoader(fileName,
BarDataFileFormat.Symbol_Date_Time_O_H_L_C_V_O,
DateFormat.Custom,
DateSeparator.None,
"yyyyMMdd HH:mm",
true);
IndicatorBase indicatorEMAX1 = IndicatorRegistrationManager.Instance.GetIndicator("EMAX1", timeDataSeries5Min);
// Set Indicator input value of LengthPeriod to 10 
indicatorEMAX1.SetFieldValue("LengthPeriod", 10);
for (int i = 0; i < timeDataSeries5Min.Count; i++)
{
  IBarData barData = timeDataSeries5Min[i];
  double ema10 = indicatorEMAX1[i];
}

Step 6: How to Use Your Custom Indicator

Once your indicator is available as a DLL (compiled assembly) or as a code class directly in your project, you can instantiate and use it programmatically.

6.1 Load Time Data Series
First, load or create a TimeDataSeries with your price data:

// Create a 5-minute compressed time data series 
TimeDataSeries timeDataSeries5Min = TimeDataSeries.GetMinutesBarCompression(500);
// Load CSV data 
CSVOHLCLoader csvOHLCLoader = new CSVOHLCLoader(fileName,
BarDataFileFormat.Symbol_Date_Time_O_H_L_C_V_O,
DateFormat.Custom,
DateSeparator.None,
"yyyyMMdd HH:mm",
true);

6.2 Get Indicator Instance Using Registration Manager
Use the IndicatorRegistrationManager to create an instance of your indicator by its registered name:

// Get indicator instance by name (EMAX1) and bind it to the time series 
IndicatorBase indicatorEMAX1 = IndicatorRegistrationManager.Instance.GetIndicator("EMAX1", timeDataSeries5Min);

6.3 Configure Indicator Parameters
Set any parameter values before calculating. For example, change the EMA period to 10:

// Set the LengthPeriod parameter to 10 
indicatorEMAX1.SetFieldValue("LengthPeriod", 10); 

6.4 Access Indicator Values
Iterate through the time series and retrieve the indicator value at each bar:

for (int i = 0; i < timeDataSeries5Min.Count; i++) 
{ 
    IBarData barData = timeDataSeries5Min[i]; 
    double ema10 = indicatorEMAX1[i]; 
    // Use the EMA value for your logic 
    Console.WriteLine($"Bar {i}: Close={barData.Close}, EMA10={ema10}"); 
} 

You’ve just turned a concept into a production-ready engine. Your EMAX1 is now hard-wired for the BlitzTrader ecosystem and is now optimized for seamless transition from deep-historical backtesting to precision-grade live trading.