C++ Backtest Environment — Part 2: GNUplot and Qt for Realtime Returns Stream Visualization

Evan Kirkiles
Evan Kirkiles Blog
Published in
4 min readJun 26, 2018

It’s been a while since my last post, as most of what I’ve done on the backtest has been simple structuring and mostly following Michael Halls-Moore’s tips for layout. That being said, the environment is fully functional now, and thus a nice-looking dark-themed GUI is an absolute necessity. After a bit of research I have settled on using Qt for the main control window and GNUplot’s X11 terminal for any graphing requirements. Note: As I’m writing this from the future, I tried consolidating everything into one window with QtCharts, but between the low documentation of the library and the complexity of multithreading in Qt I decided just to use GNUplot with separate windows.

The first step is to get the graph working, which I can then hook up to a Qt window control panel. GNUplot is a command-line tool, so I need to use a C++ pipe that runs the gnuplot command and writes to it. This can be done in a single line of code:

//gnuplotter.cppFILE* gnuplotPipe = popen("/usr/local/bin/gnuplot", "w");

Then, writing to the pipe with fprintf() and flushing acts as a command in the gnuplot terminal. For example, to plot a sine wave:

fprintf(gnuplotPipe, "plot sin(x) \n");
fflush(gnuplotPipe);

GNUplot supports plotting from data files, so my plan is to write the equity curve data to a file which is continuously plotted until the backtest has completed. This also allows for the flexibility to save the data to an external file and compare different backtests’ performances — a future feature, maybe. Anyways, the data to be plotted is located in a NaivePortfolio object pointer in the GNUPlotter class. Accessing it is a little bit complicated without the Python DataFrame:

//gnuplotter.cpplong date = portfolio->all_holdings.rbegin().operator*().first;
double equitycurve = portfolio->all_holdings.rbegin().operator*().second["equitycurve"];
double benchequity = benchmark->all_holdings.rbegin().operator*().second["equitycurve"];

Note that both the portfolio and the benchmark are NaivePortfolio objects and are structured essentially the same, allowing for other strategies to take the place of the benchmark rather than just the one provided. The above code simply accesses the all_holdings map of relevant performance information and takes the latest bar’s total return. With this data in hand, I can create data.csv from which to plot:

//gnuplotter.hppfstream data;//gnuplotter.cppdata.open(dataFile, ios::in | ios::out | ios::app);
data << get_std_time(date) << ", " << equitycurve*100 << ", " << benchequity*100 << "\n";
data.close();

After writing the performance to the file, plotting is again very simple. The function getEquityFormat() simply returns the string for the custom GNUplot format I am using (dark themed, of course), and get_std_time(long) converts epoch time into a date that is readable by GNUplot.

//gnuplotter.cpp// Plot the main equity curve, benchmark, and baseline
string plot2 = getEquityFormat() + string("plot \"") + string(dataFile) + string("\" using 1:2 with lines title \"ALGORITHM\" ls 1, \"\" using 1:3 with lines title \"BENCHMARK\" ls 2, 0 with lines title \"baseline\" \n");
fprintf(gnuplotPipe, "%s", plot2.c_str());
fflush(gnuplotPipe);

On top of the main equity curve graph, I also implemented another graph for plotting the distributions of the portfolio’s holdings. This one takes the form of a stacked bar plot, which is quite graphically intensive when replotting so will be toggleable in the Qt control panel. The structure is essentially the same so I will refrain from writing the code here — I cycled through all the symbols and recorded what percentage of the total holdings each made up, appending these to another .csv file and adding a new line to a new set of plot instructions for every symbol. The code is on my GitHub if you are interested. With these two graphs complete, the UI has begun to take shape:

Not bad. Still, all backtest variables are being set in the code, which is difficult to change quickly and easily. Thus, the next step is to create the main Qt control panel window. The fields that need to be accessible are the start date, end date, initial capital, and a checkbox for whether to display the holdings graph or not (performance is better when this is disabled). To do this, I must make the control window have access to each of the components of the backtest, instantiated in main.cpp and then passed by reference through the constructor of the window class to instance variables. As the backtest components require access to the variables being changed by the window, those must also be globalized through pointers:

//main.cppint main(int argc, char **argv) {...Instantiate all backtest components and variables...QApplication app(arc, argv);AlgoWindow window(&interface, &strat, &bench, &gnuplot, &startdate, &enddate, &initialcapital, &showholdings);
window.show();
window.raise();
app.exec();return 0;}

Then, in qtwindow.cpp, I can create different types of QWidgets for each variable to be changed, modifying the globalized variable through the window. I also add a button to initialize and run the backtest, and various QLabels that display performance statistics. The majority of this large class is simply formatting and tweaking QWidget properties, so I will refrain from posting it here. Again, everything is on my GitHub.

With the Qt window set up, the interface truly looks presentable:

The next step is to build upon this simple foundation with more nuanced features such as slippage simulation and a Monte Carlo analysis system.

All code can be found on my GitHub: https://github.com/evankirkiles/backtest-environment

--

--