Strategy Testing & Optimization in Standalone C Programs

Introduction

In this article, I provide and briefly describe a sample command-line C program that includes: parsing of MT4-exported CSV files, collection of statistical information, a simple trading system based on the collected statistics, a trading simulator, and trivial parameter optimization loops around both the statistics collection and the trading simulation phases.

Although I wrote this program earlier (as a hack for personal use), its publication is inspired by the interest to a similar development approach used by Better - a participant of Automated Trading Championship 2007, who holds the first place at the time of this writing.

Why a standalone program?

There are several reasons why it may be preferable to test and optimize a strategy in a standalone program first:

Operating environment

The program was only tested on Linux x86 and x86-64 so far, compiled with gcc 3.4.5. (For the curious, the 32-bit one of these Linux systems also happily runs MT4 under Wine 0.9.35, except for the two major issues with the strategy tester mentioned above. I don't use Windows.)

However, the source code should be portable to other systems and C compilers. Specifically, it should be trivial and convenient to compile and use it with Cygwin on Windows (for anyone who is comfortable with the command line).

Program settings, inputs, and output

There are a number of compile-time settings within the code. These control ranges for the parameters to be optimized; the parameters themselves are (very) briefly described with comments nearby to the corresponding #define directives. Additionally, some sections of code are within #if 1 ... #endif and #if 0 ... #endif blocks - you can turn them on/off as needed. (Yes, this was a quick hack.)

The program, in its present form, takes one or two CSV files (as exported from MT4) as input. If two files are given, the first one is used for statistics collection and the second one is used for strategy testing. Typically, the files would correspond to the same financial instrument and the same timescale (and likely the same source of historical data), but for different and non-overlapping time periods. That way, you make sure that the program "learns" rather than "memorizes". (A less significant "memory effect" is still possible via the optimized parameters. To avoid it, you'd need to fix the parameters at certain values and re-run the program on yet another time period.) However, if only one file is given, it will be used for both purposes - which is useful as a sanity test while debugging.

The program output varies upon the enabled #if ... #endif sections and trading strategy success (or lack thereof). As provided for this article, the program will attempt to optimize its parameters using ranges and steps suitable for 4-hour historical data for EURUSD or similar (you'd need to reduce the ranges and steps for smaller timeframes). Then it will output the trading results and the corresponding parameter values for sufficiently successful tests only, as defined by a hard-coded check (net profit of at least 1% and larger than maximum drawdown, at least 20 trades made). Results that are not good enough will be skipped.

Compiling and invoking the program

Compile with gcc (say, on a Unix-like system or on Windows with Cygwin):

gcc fx2.c -o fx2 -Wall -s -O3 -fomit-frame-pointer -ffast-math

(other optimization flags may be added as appropriate for the machine).

Invoke the program from a Unix shell (e.g., bash) on one file for testing:

./fx2 data/alpari/EURUSD240.csv

The output will be like:

balance = 1303.90 (min 999.90, max 1308.40); drawdown = 64.40; trades = 23
gross p/l = 390.40/86.50; profit factor = 4.51; expected payoff = 13.21
SL=0.1000 TP=0.1000 T=0.0002/0.0012 TCTO=3 TMEO=0.0003 TCTC=3 TMEC=-0.0017
balance = 1277.00 (min 999.90, max 1281.50); drawdown = 106.70; trades = 62
gross p/l = 461.80/184.80; profit factor = 2.50; expected payoff = 4.47
SL=0.1000 TP=0.1000 T=0.0002/0.0012 TCTO=3 TMEO=0.0003 TCTC=3 TMEC=-0.0012

...and so on.

Invoke the program on two files, for different time periods:

./fx2 data/alpari/EURUSD240.csv-1 data/alpari/EURUSD240.csv-2

And receive similar output, except that profits will likely be less and optimized parameter sets will be different (hopefully, there are some that overlap with the test above):

Parsing file #1: data/alpari/EURUSD240.csv-1
Parsing file #2: data/alpari/EURUSD240.csv-2
balance = 1116.50 (min 1000.00, max 1136.40); drawdown = 52.20; trades = 42
gross p/l = 221.60/112.90; profit factor = 1.96; expected payoff = 2.59
SL=0.1000 TP=0.1000 T=0.0002/0.0012 TCTO=3 TMEO=0.0003 TCTC=3 TMEC=-0.0017
balance = 1115.70 (min 999.30, max 1135.60); drawdown = 52.20; trades = 42
gross p/l = 220.80/112.90; profit factor = 1.96; expected payoff = 2.57
SL=0.1000 TP=0.1000 T=0.0002/0.0012 TCTO=3 TMEO=0.0008 TCTC=3 TMEC=-0.0017

...and so on.

Unfortunately, there's still some "memory effect" resulting from optimization of the parameters based on both input files (so the trading system might not perform that well in practice), but luckily discussing it further is out of the scope of this article. ;-)

What's next?

The provided program is just an example to get you started. Be creative.

Where do I get the program, again?

Download the C source for the program here (10 KB).

Quick Comment:

49296