XFinChart Charting Library — Make your own Indicators (Part 2)

Aug 24 2020
 

“Never, ever argue with your trading system.”

Itwill never get you anywhere! And let’s face it, it’s probably not the systems’ fault. If it’s really doing you no good, change it! This quote by bestselling author Michael Covel, co-founder of popular trading website trendfollowing.com emphasizes that a trader is only as good as his trading system. Technical Indicators are the backbone of any modern-day Technical Analysis system. They play a vital role in identifying accurate buy/sell signals. This is essential in booking profits on time and cutting losses in case of adverse market movement.

This is the second blog in a 3 part series containing a detailed code walk-through of XFinChart’s Indicator and Signals functionalities. The basics have been covered in part 1 of the series. Please go through it here before proceeding with this blog.

Image for post

I’m Aniket Niranjan Mishra, and I am a fourth-year undergraduate student at the Indian Institute of Technology, Kharagpur. I am currently enrolled in the Dual Degree course in Metallurgical and Materials Engineering with specialization in Financial Engineering. Having developed a keen interest in the field of Algorithmic Trading and Quantitative Finance lately, I interned as a Software Developer at Symphony Fintech Solutions Pvt. Ltd. in Summer 2020. I was a part of the High-Frequency Trading Development Team and worked directly on the tools discussed in this blog series. You can find more about me here, I will be more than happy to clear any doubts that you have and listen to your insights.

Making the Main User Project

Starting with the project we created in the last blog (Part 1), in the main project, we have created a form, which has clickable buttons which when pressed by the user, a particular indicator is run on the mentioned data file and displays/stores the calculated values. In the MainForm function, first, all inbuilt indicators have to be registered using the code below:-

IndicatorRegistrationManager.Instance.RegisterAllInBuiltIndicators();
List<string> indicatorNameList = IndicatorRegistrationManager.Instance.GetIndicatorNames();
indicatorNameList.Sort();
Indicator registration

The data that is read from the text file source using the File Parser will be stored in a TimeDataSeries object. Users can use this to create an object that represents an OHLCV time data series. The class is used for historical data, and real-time bar build based on last traded price events. The declaration is done in the following way:-

_timeDataSeries = new TimeDataSeries(BarCompression.MinuteBar, 60, BarType.CandleStick);

 

The TimeDataSeries object provides a lot of flexibility to the user. It gives the option to set the compression for the bar using the BarCompression. Different types of compression available are MinuteBar, DailyBar, WeeklyBar, MonthlyBar, and YearlyBar. All the price information for these periods is compressed according to the value of BarCompression. The BarType option gives the option to select either Heiken-Ashi or Candlestick type bar. Further details on these two types can be found here. The function LoadDataFromFile associated with the TimeDataSeries object is used to load data into the object.

_timeDataSeries.LoadDataFromFile(fileName, OHLCFileFormat.Symbol_Date_Time_O_H_L_C_V_O, DateFormat.Year_Month_Day);

 

The advantage of using the LoadDataFromFile is that it implements the reading functionality through a buffer. This results in swift reading action, millions of lines of price volume data can easily be read in milliseconds. Compared to the conventional line by line input, this performs way faster. The filename has to be specified with full path, or if the file is present in the same folder as the project, then it can be named as

string fileName = @"ACC.txt";

 

The OHLCFileFormat option enables the user to specify the format in which data is present in the input file. The supported formats are Symbol_Date_Time_O_H_L_C_V_O, Date_Time_O_H_L_C_V_O, and Date_O_H_L_C_V.

After the above steps, the user can make any form/application he needs, and then add further code to the OnButtonClick object to run a particular indicator. Information for creating a custom Windows form is available here. The further part will be explained, taking the example of the Shinohara Ratio Indicator, which is built in the library. Reference material for background on the Shinohara indicator can be found here.

Creating the Custom Indicator File

In the IndicatorName.cs file, after including the necessary namespaces, the first thing that has to be done is to decorate the indicator implementation class with the Indicator Attribute:-

[IndicatorScaleAttribute(false)]
[IndicatorAttribute("{201CF829-E573-472B-9FC2-DCCA60CAC9F4}", "shinohara", "Shinohara Intensity Ratio", "MYIndicator")]

 

The IndicatorScaleAttribute is used to signify whether the value of the indicator will be bounded, like RSI or unbounded like Shinohara Indicator. To set the indicator scale for an indicator bounded between 0 and 100, do the following.

[IndicatorScaleAttribute(IndicatorScale.custom,0,100)]

 

The first argument in IndicatorAttribute is ConstantID, which is a GUID to uniquely identify the indicator. The next argument is the name of the indicator. This will be used in the main application to access the indicator, so set the name accordingly. The next item, Description, provides a few words explanation of the indicator for documentation purposes. Finally, the Owner field specifies the name of the entity that develops the indicator. It can be set to any custom name in the end-user application. Further code has to be written inside the public indicator class Shinohara that derives from IndicatorBase. All indicators have to inherit from IndicatorBase to be able to have access to all the built-in features.

Once inside the indicator class, first, the Inputs and Outputs have to be described. The indicator always requires the TimeDataSeries object as input, so it does not have to be explicitly mentioned. Inputs here means parameters that can be changed by the user to modify the indicator output. For example, in the case of Simple Moving Average, the window of averaging has to be taken as input. Any variable decorated with Parameter Attribute is treated as user input. The framework allows the user to change the value during run-time to calibrate the output series. The only input parameter needed in Shinohara Indicator is the length of the period used for calculation. All the input parameters can be provided with a default value used in case the user does not signify parameter value. Here the default value has been set to 26.

[Parameter("LengthPeriod", Description = "The length of the Shinohara", DefaultValue = 26)]
private int _lengthPeriod = 26;

 

The output is always a DoubleSeries object, and its length is equal to TimeDataSeries length. For each value of bar data in TimeDatSeries input, the Indicator implementation must define a corresponding indicator value. It can be Double.NaN if the indicator logic does not provide a value to a particular index. The first arguments (ShinoharaAratio, ShinoharaBratio) are the names that will be used to access the output series in the main application. The LineColor signifies the color the output line will have in the charting module.

[Output("ShinoharaAratio", DefaultValue = double.NaN, LineColor = "DarkOrange")]
private DoubleSeries _aDoubleValueSeries = new DoubleSeries();

[Output("ShinoharaBratio", DefaultValue = double.NaN, LineColor = "DeepSkyBlue")]
private DoubleSeries _bDoubleValueSeries = new DoubleSeries();

 

Next, there are multiple constructor functions for the indicator class. They are overloaded (have the same name), but they differ in the parameters required to call them. These take care of situations when the user may not give input values for all parameters and just wants to use the default values. They have different values missed out in their definitions to handle all possible initialization cases. The constructors are used to initialize variables, mainly such as indicators, data series, lists, etc. In general, any variables that need to be calculated only once on startup should be in the constructor to avoid unnecessary consumption of resources.

public Shinohara() : base(null){
}

public Shinohara(TimeDataSeries timeDataSeries, int lengthPeriod) : base(timeDataSeries){
     if (lengthPeriod <= 0)
        throw new ArgumentException("Invalid Shinohara length");            
     _lengthPeriod = lengthPeriod;       
}
public Shinohara(TimeDataSeries timeDataSeries) : base(timeDataSeries){
}

 

The Calculate method is the primary method of the indicator. It is called for each historic bar starting from the beginning of the series up to the current bar and then on each incoming bar. It is typically used for the calculation of an indicator based on a formula. In the case of Shinohara Indicator, we have the formulae for Shinohara A and Shinohara B ratios. The index parameter represents each bar, ranging from 0 up to the current (last) bar. So, at each bar, the Calculate method will be called. The Result will be returned and also stored in the two DoubleSeries Shinohara A and B. Any temporary variables that need to be created for the calculation of the indicator values can be done inside the Calculate function.

protected override double Calculate(int index){
    if (index<_lengthPeriod){
        _aDoubleValueSeries[index] = double.NaN;
        _bDoubleValueSeries[index] = double.NaN;
    }else{
        double abull = 0.0;
        double abear = 0.0;
        double bbull = 0.0;
        double bbear = 0.0;
        for(int i=index-_lengthPeriod+1;i<=index;i++){
            abull += (TimeDataSeries[i].High - TimeDataSeries[i].Low);
            abear += (TimeDataSeries[i].Open - TimeDataSeries[i].Low);
            bbull += (TimeDataSeries[i].High - TimeDataSeries[i - 1].Close);
            bbear += (TimeDataSeries[i-1].Close - TimeDataSeries[i].Low);
        }
        _aDoubleValueSeries[index] = Math.Round(100 * (abull / abear), 2);
        _bDoubleValueSeries[index] = Math.Round(100 * (bbull / bbear), 2);
    }
    return (_aDoubleValueSeries[index]);
}

 

The TimeDataSeries[index].High type method can be used to access the Open/High/Low/Close/Volume of the current index bar. The Math.Round method has been used for rounding off the indicator value so that on printing, it looks presentable. If accuracy is desired over presentability, this rounding should be avoided to store higher precision double values. Any index that does not have a value should be assigned a NaN value.

Finally, the ResetIndicator method resets the variables, series, and any other indicators used. It returns them to their null, empty state so that next time the indicator is used, the values do not overlap with the values from the previous run.

protected override void ResetIndicator(){
    _aDoubleValueSeries = new DoubleSeries();
    _bDoubleValueSeries = new DoubleSeries();
}

 

These were all the components that are a part of the indicator file. When making a new indicator of their own, the user should save all the above code in a file, IndicatorName.cs. It has to be present in the project folder for the main application to access it and use it for calculation. The name and GUID of the new custom indicator should not clash with any inbuilt indicator. Any type of clash will lead to errors during runtime.

Continuing with the Main User Project File

Now, in the main application, here MainForm.cs, the following section illustrates how to use the user’s indicator just created in the previous section. In the OnButtonClick() function, the following code should be added to access the indicator and interact with it in the main application.

public void AddShinoharaX(){
    Type indicatorType = typeof(Shinohara);
    IndicatorRegistrationManager.Instance.RegisterIndicator(indicatorType);

    string fileName = @"C:\Users\Optimus\Desktop\Symphony\XFinChart\XFinChartSimpleTester\XFinChartSimpleTester\bin\Debug\ACC.txt";
    TimeDataSeries timeDataseries = new TimeDataSeries(BarCompression.MinuteBar, 60, BarType.CandleStick);
    timeDataseries.LoadDataFromFile(fileName, OHLCFileFormat.Symbol_Date_Time_O_H_L_C_V_O, DateFormat.Year_Month_Day);

    IndicatorBase indicator = IndicatorRegistrationManager.Instance.GetIndicator("shinohara", timeDataseries);
    if (indicator == null)
        return;

    indicator.SetFieldValue("LengthPeriod", 26);
    indicator.Refresh(timeDataseries.Count);
    DoubleSeries shinoharaADoubleSeries = indicator["ShinoharaAratio"];
    DoubleSeries shinoharaBDoubleSeries = indicator["ShinoharaBratio"];            

    for (int currentIndex = 0; currentIndex < timeDataseries.Count - 1; currentIndex++)    {
        Console.WriteLine("{0}:- Open:{1} High:{2} Low:{3} Close:{4} ShinoharaA:{5} ShinoharaB:{6}",
        currentIndex, timeDataseries[currentIndex].Open, timeDataseries[currentIndex].High, timeDataseries[currentIndex].Low,
        timeDataseries[currentIndex].Close, shinoharaADoubleSeries[currentIndex], shinoharaBDoubleSeries[currentIndex]);
    }
}

 

The AddShinoharaX() method is just like the OnButtonClick() function. The indicatorType specifies which namespace to derive the indicator from. The argument of typeof() function should be the name of the file in which the indicator is defined. Here it is, Shinohara. The RegisterIndicator object then registers the indicator specified through the previous indicatorType, so that it can be used ahead. The further steps containing the creation of DataSeries object and reading data into it have been explained earlier.

The indicator object of type IndicatorBase is used as a proxy for the indicator. It is assigned the proper indicator using the GetIndicator function. It has to be appropriately assigned for further operations. A null check after the assignment ensures that the assignment was completed correctly. If the indicator object is null due to improper initialization, the function returns. The further parts of AddShinoharaX() are not executed in this case then.

The SetFieldValue method can be used to set the values for any parameters used in the indicator. The name in quotes has to match with the name mentioned in the Input section of the Indicator file. This step can be skipped if the user wants the indicator to use the default parameter values. The Refresh function calculates the indicator values for all the bars in input data. This command is used to avoid running the indicator for every bar index manually. The indicator[“OutputField”] method is used to get the outputs from the indicator in the form of a DoubleSeries object. The name has to be the same as that declared in indicator’s Output attributes.

Finally, the for loop prints the indicator and OHLC values for each bar in the required format. This section can be used for checking if the indicator calculation was done correctly for each bar index.

This was all that is needed for making your own indicator and how to use it in your own application like a Windows Form. In the next blog, I will be illustrating how to make a custom signal/strategy for actual trading.

Thanks.

Link to Part 1:- XFinChart Charting Library — An Introduction (Part 1)

Link to Part 3:- XFinChart Charting Library — Make your own Trading Signals (Part 3)

Contact US

Let us help you to achieve your goals!

 
 
 
 
 
By providing Symphony with your contact information Symphony will process your personal data for the purpose of providing you with the information you have requested. For more information regarding Symphony's processing of your personal data, please read Symphony's Privacy Notice here.

  • Get latest updates from Us