XFinChart Charting Library — Make your own Trading Signals (Part 3)

Aug 24 2020

“The four most dangerous words in investing are: This time it’s different.”

InSir John Templeton’s timeless 16 rules for Investment Success (published in 1933), he states: “The investor who says, ‘This time is different,’ when in fact it’s virtually a repeat of an earlier situation, has uttered among the four most costly words in the annals of investing.” Investor and mutual fund pioneer, Templeton became a billionaire by pioneering the use of globally diversified mutual funds. Templeton wants to emphasize that at stock market tops and bottoms, investors invariably use this rationale to justify their emotion-driven decisions.

Knowing how to control emotions while trading can prove to be the difference between success and failure. Your mental state has a significant impact on the decisions you make, mainly if you are new to trading. Keeping a calm demeanor is essential for consistent trading. The best way to keep your emotions out of trading systems is to follow a robust and well-defined trading logic without interfering with it. Here’s where the Signals functionality of the XFinChart library comes handy. It allows the user to write custom trading logic and back-test them on real-time/past data.

This blog is the 3rd and last in a 3 part Blog series explaining the concepts of the XFinChart library by Symphony Fintech Solutions Pvt. Ltd. Please go through Part 1 and Part 2 before continuing with this blog. They contain vital concepts needed to properly understand the concepts of 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.

Same as in the custom indicator application discussed in Part 2, we will start with the Main Application used for Signals demonstration. Here we will be using TSTestApp application for demonstration purposes. It is a straightforward Windows Form application with a button which, when pressed, will run the strategy/signal TestSignal on the mentioned data file. Information for creating a custom Windows form can be accessed here. Here onward, strategy and signal terms will be used interchangeably, and they convey the same meaning. The overall functionality is the same as the main application in the indicators section.

To start with, add the following DLLs in your project reference section to access the SignalBase and other underlying structures.

DLL/ XTS.TS.Core.dll

In the OnButtonClick function (here btnRunStrategy_Click), the following code has to be added to execute the strategy.

private void btnRunStrategy_Click(object sender, EventArgs e){
    string fileName = @"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);
    SignalBase signalBase = new TestSignal();
    StrategyConfigData strategyConfigData = new StrategyConfigData();
    Instrument instrument = new Instrument(111, "NSECM", "ACC", 20, 1, DateTime.MaxValue);
    TSExecutor tsExecutor = new TSExecutor(instrument, timeDataseries, strategyConfigData, signalBase);
    tsExecutor.TSLogEventHandler += new TSLogDelegate(this.LogText);

    Dictionary<string, double> statistics = tsExecutor.GetCloseTradeStatistics();
    foreach(KeyValuePair<string, double> statsEntry in statistics){
        LogText(String.Format("{0,-50}{1,-100}", statsEntry.Key, statsEntry.Value));


The filename variable stores the name of the file that contains the price volume data of the security the strategy will be run on. The creation of TimeDataSeries object and the process of reading in the data is precisely the same as those in the Indicators blog (Part 2). Please refer to that for further details.

A new SignalBase object has to be created, it has been named signalbase here. This enables the object to derive all the inbuilt functionality of SignalBase. The variable is assigned a new TestSignal instance.

Next, we will look into how the TestSignal has been coded in the TestSignal.cs file. The file TestSignal.cs has to be included in the main application’s namespace to enable access to it. The overall structure of the TestSignal file is similar to the Indicators we created in the previous section.

[SignalAttribute("{B5CE5749-7D47-4E0C-A9C3-3770B114DA0D}", "TestSignal", "Test Signal", "SYMPH")]

Like the indicator attribute, the signal class has to be decorated with SignalAttribute, so it can be registered and accessed in the main application. The first argument is GUID, which uniquely identifies the signal. Next are the name and description arguments, finally followed by the owner’s name, which signifies who built the indicator. Here it is SYMPH as this test signal has been developed by Symphony Fintech for illustration purposes.

[Parameter("FastLengthPeriod", Description = "The fast length of the EMA", DefaultValue = 12)]
private int _fastLengthPeriod = 5;

[Parameter("SlowLengthPeriod", Description = "The slow length of the EMA", DefaultValue = 15)]
private int _slowLengthPeriod = 12;


Next, the inputs to the signal have to be defined. They have a default value used when the user does not assign any value to these parameters while calling the signal. All the inputs have to be decorated by the Parameter Attribute so that the SignalBase can recognize them as valid input parameters. Here two inputs have been taken as the length period values for both the moving averages. The default value for the fast-moving average is and for the slow-moving average is 12.

private IndicatorBase _slowEMAIndicator;
private IndicatorBase _fastEMAIndicator;

bool _isLastSignalEnterLong = fal

The next two indicators are created of type IndicatorBase, which will act as the slow and fast-moving averages. The _isLastSignalEnterLong variable denotes whether the portfolio is currently long or not. It helps in deciding how to go short if there is a short signal received.

Next, there are constructors where the user can do the initialization of the indicators and the variables on call. This approach has been illustrated clearly in the Indicators blog. Here a different approach has been demonstrated, which involves leaving the constructor empty; it does not perform any initialization. Instead, the initialization part is performed by the Initialize function.

public TestSignal(){}

protected override void Initialize(){
    _slowEMAIndicator = IndicatorRegistrationManager.Instance.GetIndicator("EMA", this.TimeDataSeries);
    _fastEMAIndicator = IndicatorRegistrationManager.Instance.GetIndicator("EMA", this.TimeDataSeries);

    _slowEMAIndicator.SetFieldValue("LengthPeriod", _slowLengthPeriod);
    _fastEMAIndicator.SetFieldValue("LengthPeriod", _fastLengthPeriod);


The constructor TestSignal() has been left empty. The Initialize function is called whenever the signal is called first, so all the necessary initialization should be done here to avoid unnecessary use of computing and memory resources.
In the Initialize function, the indicator variables created earlier are assigned the required indicator using the GetIndicator function. Here the EMA indicator is used for both. Next, the parameters in the EMA indicators are assigned their values using the SetFieldValue function. Similar to how the main calculation part of the Indicator file was done in the Calculate() function, the signal’s main logic is contained in the ProcessSignal() function.

public override void ProcessSignal(){
    int contractSize = 1;

    if (CurrentIndex < _slowLengthPeriod)

    double test1 = _fastEMAIndicator[CurrentIndex];
    double test2 = _slowEMAIndicator[CurrentIndex];

    double fastEMAVal = _fastEMAIndicator["EMA", CurrentIndex];

    CrossOver crossover = CrossOver.None;
    crossover = _fastEMAIndicator["EMA"].Cross(_slowEMAIndicator["EMA", CurrentIndex]);

    if (crossover == CrossOver.Above && _isLastSignalEnterLong == false){
        EnterLongAtMarket(contractSize, "EnterLong");
        _isLastSignalEnterLong = true;
    } else if (crossover == CrossOver.Below && _isLastSignalEnterLong == true){
        EnterShortAtMarket(contractSize, "EnterShort");
        _isLastSignalEnterLong = false;


The ContractSize determines how many lots of the security to buy/sell at one time. The strategy logic in this example is that whenever crossover occurs between the fast and slow-moving averages, a signal is generated depending on the crossover’s direction. The if-else block executes this and calls the EnterLongAtMarket and EnterShortAtMarket to place market buy and sell orders. The value of isLastSignalEnterLong variable is changed accordingly to reflect the current position.

A new variable of type StrategyConfigData has to be created that will store the configuration parameters of the strategy. Next, the instrument on which the strategy will be run is defined.

Instrument instrument = new Instrument(111, "NSECM", "ACC", 20, 1, DateTime.MaxValue);

In the first argument, the InstrumentID uniquely identifies the instrument that is to be traded. The market segment defines which segment the instrument belongs to, followed by the name of the security(ticker). These are then followed by the tick size and expiry date of the instrument. In the case of securities like options, expiry date is an essential identifier for the security. In the case of ordinary equity stocks like ACC in this example, ideally,they do not expire, so the expiry date is set as DateTime.MaxValue. All the information that has been declared till now is now passed to the TSExecutor, which actually executes the strategy on the given data.

TSExecutor tsExecutor = new TSExecutor(instrument, timeDataseries, strategyConfigData, signalBase);
tsExecutor.TSLogEventHandler += new TSLogDelegate(this.LogText); //this handles the printing part in the output box in the form

The final part creates a dictionary and stores all the Trade Statistics like profit, the number of trades, and drawdown. Then using a for loop, all the statistics have been printed.

Dictionary<string, double> statistics = tsExecutor.GetCloseTradeStatistics();
foreach (KeyValuePair<string, double> statsEntry in statistics){
    LogText(String.Format("{0,-50}{1,-100}", statsEntry.Key, statsEntry.Value));


This is all that is needed to run a custom strategy using the back-testing engine created by Symphony Fintech. It provides full flexibility regarding custom indicators and signals, as illustrated in these blogs.

With the development of numerous trading strategies, the options for analysis tools have increased. Thus, choosing the right tools and configuring the correct parameters for them has become increasingly important. But coming up with the right set of configurations for every indicator, for each stock isn’t as straightforward. To counter this problem, the XFinChart library has a unique feature that gives you stock-specific suggestions for the ideal set of parameters for your signal. To read more about the feature refer to this blog.


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

Link to Part 2:- XFinChart Charting Library — Make your own Indicators (Part 2)

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