Previous: Script elements

Workshop 4: Trend Trading. Price Series. Backtests.

Prediction is difficult. Especially about the future.
- Niels Bohr

The point of trading is knowing the moment when it's good to buy, good to sell, or good to do nothing. A trade strategy uses market inefficiencies - deviations of the price curves from random data - for predicting future prices and finding the right buying and selling points. That will be the topic of the next workshops. Two things to keep in mind:

► All strategies presented here are meant for educational purposes. They all are designed for simplicity, not for maximum profit or robustness. For really trading such a strategy, you would normally add more entry filter rules, and you would also use a more complex trade exit method than a simple stop. But we'll keep it easy in the workshops.

The backtest results included here can be different to the results you'll get when testing the scripts yourself. That's because you're most likely using a more recent simulation time period, and different spread, commission, and rollover parameters which are updated when connecting to a broker. If not otherwise mentioned, the included scripts are set to a simulation period from 2010-2016; for a different time period modify the StartDate and EndDate lines in the script.

Bob got a problem

The most obvious way to make profits is going with the trend. Here trader Bob tries to explain his trade strategy to programmer Alice. Bob has just hired her to automatize his system, since he does not trust his trading instinct anymore, and wants to replace himself with an algorithm:

Bob: I go with the trend. I buy long when prices start going up and I go short when they start going down.
Alice: 'Going up' means that today's price is higher than yesterday's price?
Bob: Nah, one single higher price alone won't do. Prices wiggle a lot. I look for long term trend, like the trend of the last month. I do that with a moving average. That means I just sum up the prices of the last 4 weeks and take their average.
Alice: That shouldn't be a problem to automatize.
Bob: Well, actually there is a problem. You see, a moving average is not very good for trend finding. It lags a lot behind the prices. The signals are just not timely for a good trade. The trend is already over when my moving average finally bends up or down.
Alice: Instead of a moving average, I could use a second order lowpass filter. It has almost no lag.
Bob: I trust you.
Alice: How shall I exit trades? I could exit a long position when entering a short one and vice versa.
Bob: Yeah, that's what I normally do. We also need a stop loss.
Alice: At which price?
Bob: Not too far and not too tight. I don't want to lose too much, but I also don't want my trades stopped out all the time.

Following the conversation, Alice wrote this trade strategy script for Bob (Workshop4):

function run()
  vars Price = series(price(0));
  vars Trend = series(LowPass(Price,500));
  Stop = 4*ATR(100);

  vars MMI_Raw = series(MMI(Price,300));
  vars MMI_Smooth = series(LowPass(MMI_Raw,300));
  if(falling(MMI_Smooth)) {
    else if(peak(Trend))

(If you're not yet familiar with scripts, start with Workshop 1.) We can see that the function is now named "run" and not "main". While a main function runs only once, a run function is called after every bar with the period and asset selected with the scrollbars. By default, the bar period is 60 minutes. So this function runs once per hour when Zorro is trading.

The vars definition creates not a single var here, but a var series - a var 'with a history'. (C++ programmers who peeked into the lite-C headers might have noticed that vars is in fact a double* pointer, but that needs not bother us here). A series is defined with the type vars and a series() call with the value of the series. A series begins with the current value, followed by the value from one bar period before, followed by the value from two bar periods before and so on. Series are mostly used for price curves and other data curves. Thus, vars Price = series(price(0)); means: define a series with the name "Price" and fill it with the return value of the price(0) function.

The following line defines a series named "Trend" and fills it with the return value from the LowPass function, Alice's second order lowpass filter. Its parameters are the previously defined Price series and a time period, 300 bars. The lowpass filter attenuates all the wiggles and jaggies of the Price series that are shorter than 4 weeks, but it does not affect the trend or long term cycles. You can see the frequency characteristic of the LowPass filter in the image on the Filter page.

In the image below, the black line is the original EUR/USD price and the red line is the result from the LowPass function:

A lowpass filter has a similar smoothing effect as a Moving Average function (see Indicators), but has the advantages of a better reproduction of the price curve, and less lag. This means the return value of a lowpass filter function isn't as delayed as the return value of a Moving Average function that is normally used for trend trading. The script can react faster on price changes, and thus generate better profit.

The next line places a stop loss. Stop is a predefined variable, determining the maximum allowed loss of the trade. The limit here is given by 4*ATR(100). The ATR function is a standard indicator. It returns the Average True Range - meaning the average height of a candle - within the last 100 bars. So the position is sold when the loss exceeds the average size of four candles. By setting Stop not at a fixed value, but at a value dependent on the price fluctuation, Alice adapts the stop loss to the market situation.

Any strategy should have a mechanism for detecting if the exploited price anomaly is present or not. In this case, if the market is trending or not. Many indicators have been invented for detecting trending and non-trending market situations, with more or less success. Alice uses the Market Meanness Index, an indiator that performs a statistical analysis of the data. Alice has calculated the MMI for the last 300 bars and smoothed it with the LowPass filter.

Trades are entered when the smoothed MMI is falling, indicating the begin of a trend. The valley function returns true when the series just had a downwards peak. The peak function returns true when the series just had an upwards peak. The if(..) condition is then fullfilled, and a long or short trade with the selected asset is entered. If a trade was already open in the opposite direction, it is automatically closed. 

An example trade triggered by this strategy:

The red line in the chart above is the Trend series, the lowpass filtered price. You can see that it has a peak at the end of September, so the peak(Trend) function returned true and enterShort was called. The tiny green dot is the moment where the short trade was entered. The Trend series continues down all the way until November 23, when a valley was reached. A long trade (not shown in this chart) was then entered and the short trade was automatically closed. The green line connects the entry and exit points of the trade. It was open almost 2 months, and made a profit of ~ 13 cents per unit, or 1300 pips.


Now let's just test how buying and selling works in that strategy. Start up Zorro, select the [Workshop4] script and the [EUR/USD] asset, leave the [Period] slider at 60 minutes, then click [Test]:

You'll most likely get a different result when testing this strategy in a different time period or when your simulated account has different spread, rollover costs, commission, or slippage. By default, the simulation runs over the last 6 years, f.i. from 2010 until 2015. The strategy achieves an annual profit of ~400 pips, equivalent to about 30% annual return on capital - that's the average profit per year divided by the sum of maximum drawdown and used margin. The average monthly income (MI in the window) is 3 $. That's quite modest, but Zorro simulated a microlot account and needed only ~100 $ capital for running the strategy. So you have about 3% return on capital per month. By the way, the '$' sign in Zorro's messages does not necessarily mean US-Dollars, it represents the account currency.

The equity curve can be seen by clicking [Result]:

In the image that pops up in the chart viewer, you can see a black curve and some green lines and red dots attached to the curve. In the background there's a jaggy blue area and a red area below. The black curve is the price of the selected asset - the EUR/USD. The price scale is on the left side of the chart. The green and red dots are winning and losing trades. The green lines connect the entry and exit point of a winning trade. You can see that there are far more red than green dots - about 80% of the trades are lost. However, the long-term trades all have green lines. So we have a lot of small losses, but several large wins. This is typical of a trend following strategy.

The most interesting part of the chart is the blue area that represents the equity curve. We can see that it's slightly below zero in the first years, then rises to about 300 $ in 2016. The red area below is it's evil counterpart, the "underwater equity" or drawdown curve that indicates losses on our account. The more blue and the less red, the better is the strategy. This one gives a mixed result. There are a few winning years, in the other years we had a loss or a tie. This shaky behavior is reflected in the low Sharpe Ratio and the high Ulcer Index (UI) of the strategy.

Analyzing the trades

Since Alice wanted to check the single trades of this strategy in detail, she has set the LOGFILE flag. Such flags are turned on with the set() function. If the flag is activated, [Test] stores a log of all events in the Log subfolder. The log is also opened with the script editor on clicking [Result]. It begins with a list similar to this one:

BackTest: Workshop4 EUR/USD 2008..2013
[139: Thu 10.01. 07:00]  1.46810
[140: Thu 10.01. 08:00]  1.46852
[141: Thu 10.01. 09:00]  1.46736
[142: Thu 10.01. 10:00]  1.46721
[EUR/USD::S4300] Short 1@1.4676 Risk 6 at 10:00
[143: Thu 10.01. 11:00]  0p 0/1
[EUR/USD::S4300] Reverse 1@1.4694: -1.52 at 11:00
[EUR/USD::L4400] Long 1@1.4694 Risk 6 at 11:00
[144: Thu 10.01. 12:00]  -20p 0/2
[145: Thu 10.01. 13:00]  -20p 0/2
[146: Thu 10.01. 14:00]  -20p 0/2
[EUR/USD::L4400] Reverse 1@1.4649: -3.54 at 14:00
[EUR/USD::S4700] Short 1@1.4649 Risk 6 at 14:00
[147: Thu 10.01. 15:00]  -67p 0/3
[EUR/USD::S4700] Stop 1@1.4729: -6.22 at 15:00
[148: Thu 10.01. 16:00]  -148p 0/3
[EUR/USD::L4900] Long 1@1.4744 Risk 6 at 16:00

The meaning of the cryptic messages is explained in the trading chapter. Checking the log is the first (or maybe, second) thing to do when testing a strategy, for determining if it trades correctly. The first short trade is entered after bar number 142, starting January 10 10:00 am. Zorro bought 1 lot EUR/USD at a price of $1.4676. The trade risk was about $6, determined by the stop loss distance (due to slippage the risk can be estimated only). After one bar the short trade was closed at a loss of $1.52, the position was reversed, and a long trade was opened at a price of $1.4694. It was closed 3 bars later at a $3.54 loss.

You can see in the log that most trades are lost. Zorro seems to deliberately enter trades in the wrong direction; trading at random would only lose a little more than 50%, not 80%. Surely no human trader in his right mind would enter trades this way! But there's a method behind this madness. The algorithm wants to be in a favorable position when a long-term trend begins, and then keeps the position for a long time. That's why it wins in the long run despite losing most trades.

Aside from the log, [Result] also opens the performance report:

Test Workshop4 EUR/USD

Simulated account   AssetsFix 
Bar period          1 hour (avg 86 min)
Test period         2010-01-20..2016-12-30 (42124 bars)
Lookback period     301 bars (18 days)
Montecarlo cycles   200
Simulation mode     Realistic (slippage 5.0 sec)
Spread              0.5 pips (roll -0.02/0.01)
Commission          0.60
Contracts per lot   1000.0

Gross win/loss      1002$ / -716$ (+3275p)
Average profit      41$/year, 3.42$/month, 0.15801196b/day
Max drawdown        -145$ 50.7% (MAE -168$ 59.0%)
Total down time     71% (TAE 65%)
Max down time       122 weeks from Apr 2013
Max open margin     40$
Max open risk       27$
Trade volume        267653$ (38547$/year)
Transaction costs   -11$ spr, -2.90$ slp, -0.91499997b rol, -15$ com
Capital required    135$

Number of trades    245 (36/year, 1/week, 1/day)
Percent winning     22.0%
Max win/loss        85$ / -12$
Avg trade profit    1.16$ 13.4p (+212.9p / -43.1p)
Avg trade slippage  -0.01184432b -0.1p (+4.5p / -1.4p)
Avg trade bars      132 (+453 / -41)
Max trade bars      2792 (24 weeks)
Time in market      77%
Max open trades     4
Max loss streak     18 (uncorrelated 24)

Annual return       30%
Profit factor       1.40 (PRR 1.13)
Sharpe ratio        0.39
Kelly criterion     0.50
R2 coefficient      0.260
Ulcer index         21.0%

Confidence level     AR   DDMax  Capital

 10%                 36%   113$  114$
 20%                 33%   127$  124$
 30%                 32%   133$  128$
 40%                 30%   145$  135$
 50%                 30%   151$  139$
 60%                 27%   169$  151$
 70%                 25%   191$  166$
 80%                 23%   215$  182$
 90%                 19%   267$  216$
 95%                 16%   323$  252$
100%                 10%   541$  396$

Portfolio analysis  OptF  ProF  Win/Loss  Wgt%

EUR/USD             .080  1.40   54/191  100.0  
EUR/USD:L           .038  1.23   22/94    28.0  
EUR/USD:S           .129  1.57   32/97    72.0  
The meaning of the parameters is explained under Performance Report, or more extensively in the Black Book.

The trade distribution

For some more insight into the distribution of trades, Alice wants to plot the trade distribution. For this she adds the following line to the very begin of the script (before the run function):

#include <profile.c>

This is a command to the compiler to insert another script file from the include folder. profile.c is a script that contains functions for plotting price and trade statistics and seasonal analysis charts. For the trade distribution, Alice calls the following function from inside the run function:


This generates a trade distribution chart in steps of 50 pips at the end of the test run. The generated chart looks similar to this one:

For generating the chart, all trades are sorted into buckets, depending on their profit. Every bucket is represented by a red and a blue bar. Trades with a loss between -200..-100 pips go into the first bucket at the left side, marked -200 at the x axis. The next buckets are for trades with loss or profit from -200..-150 pips, -150..-100 pips, -100..-50 pips, and so on. The height of the blue bar is the number of trades ending up in that bucket (right y axis), the height of the red bar is the sum of all profits in the bucket (left y axis). We can see that most trades end with a loss between 0 and -50 pips. The total profit of the system comes from relatively few profitable trades, some even with about 1000 pips profit.

Aside from a lowpass filter, several other filter algorithms are often used for detecting trend changes. You can find a comparison of trend filters in the Trend Indicators article series on Financial Hacker.

What have we learned in this workshop?

Next: Walk Forward Analysis

Further reading: ► series, price, filters, stop, buy, sell, plot, performance, LOGFILE, #include, profile

Trading essentials: ► Bars, Strategies