Automated Trading Course - Part 2: Writing Strategies

Discussion in 'Automated Trading' started by jcl, Sep 14, 2012.

  1. jcl


    This is the second part of the automated trading course, which will deal with how to write a profitable strategy. Some programming knowledge is required - for this, do the first part of the course if you haven't already. It's here:

    The second part will cover writing basic trading strategies, trend and counter trend trading, optimizing, walk forward analysis, portfolio strategies, and money management. It uses some new trading algorithms such as frequency filters. To avoid misunderstandings: It's not a course about technical analysis. I won't explain here moving averages or the like. It's just about how to write a strategy based on a trading idea that you already have.

    For the course you'll need a program for running the script examples and testing the strategies. It's called "Zorro" and you can download it for free - just google for "Zorro Trading Automaton". Please also download the latest patch from the Zorro user forum, as the release had a bug in the chart display.

    Two things to keep in mind:

    ► All strategies presented in this thread are meant for educational purposes. They are all profitable, but designed for simplicity, not for maximum profit or robustness. For really trading such a strategy, you would normally add entry filter rules for filtering out unprofitable trades. How to find and generate such rules with machine learning algorithms, such as Zorro's perceptron or decision tree, might be covered in a future third part of the course. For really trading a strategy you normally also use a more sophisticated exit algorithm, realized with a trade management function. But that's also stuff for a future part and we'll keep it simple in this part of the course.

    ► I will post some backtest results here, but they can be slightly different to the results you'll get when testing the scripts yourself. That's because Zorro stores the current spread, commission, and rollover parameters for every asset when connecting to the broker. Because they change from time to time (and from broker to broker), backtest results also change. You can prevent this by setting Spread and other broker dependent parameters to a fixed value in the script, but keeping them in sync to the market gives a more realistic result.
  2. jcl


    Let's start. The point of trading is knowing the moment when it's good to buy, good to sell, or good to do nothing. Any 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. The most obvious way to make profits is going with the trend. Let's have a little stage play in which trader Bob tries to explain his trade strategy to programmer Alice. Bob has just hired her to automatize his system:

    Bob: I go with the trend. I buy long when prices start going up and I go short when they start going down.
    Alice: And this works?
    Bob: Sometimes. Depends on the market.
    Alice: So you just buy long when today's price bar is higher than the bars of the previous days?
    Bob: Nah, one higher price bar alone won't do. Prices wiggle a lot. Often the candles are all over the place. I look for the long term trend, like the trend of the last two months. I do that with a moving average.
    Alice: Good. That shouldn't be a problem to automatize.
    Bob: Well, actually there is a problem. You see, a two month moving average lags at least one month behind the price. Often the trend is already over when the average finally bends up or down. You need to sort of look ahead of the moving average curve, if you get my meaning.
    Alice: So you want to know when a two months trend changes, but you need to know it in far less time than two months?
    Bob: You got it.
    Alice: I could use a lowpass filter for getting the trend curve. Second order lowpass filters have almost no lag. Will that be ok for you?
    Bob: I dunno what a second order lowpass filter is. But I trust you.
    Alice: Good. So I buy when the trend curve changes its direction? For instance, when it starts to move up from a valley, or down from a peak?
    Bob: You got it.
    Alice: How do you exit trades?
    Bob: When it's the right time. Depends on the market.
    Alice: I can exit a long position when entering a short one and vice versa. Does this make sense?
    Bob: Yeah, that's what I normally do when I'm not stopped out earlier.
    Alice: Stopped out?
    Bob: Sure. A trade must be stopped when it's losing too much. We need a stop loss. Or do you want my whole account wiped from some bad trade?
    Alice: Certainly not before I'm paid. At which price do you place the stop loss?
    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.
    Alice: So let me guess: it depends on the market?
    Bob: You got it.

    Following the conversation, Alice wrote this trade strategy script for Bob, at a $5,000 fee:

    function run()
      var *Price = series(price());
      var *Trend = series(LowPass(Price,1000));
      Stop = 2*ATR(100);
      else if(peak(Trend))
    When you did the first part of the course, you might recognize some familar structures, such as the if statement, and some lines that look similar, but not quite like variable declarations. Tomorrow we'll go thoroughly over this script, analyze what it does line by line, and then look in detail into the behavior and performance of this strategy.
  3. sma202


    I've seen some of this stuff before...where is it plagiarised from?
  4. jcl


    From the lite-C trading tutorial. This is however a newer version.
  5. jcl


    We're going to analyze the trading code. At first, we can see that the function is now named "run" and not "main". "run" is also a special function name, but 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 trades.

    At the begin we notice two strange lines that look similar to var definitions:

    var *Price = series(price());
    var *Trend = series(LowPass(Price,1000));

    However unlike var definitions, they have an asterisk '*' before the name, and are set to the return value of a series() function call. We define not a single variable here, but a whole series. (C++ programmers might notice that we in fact define a pointer, but that needs not bother us now). A series is a variable with a history - the series begins with the current variable value, then comes the value the variable had one bar before, then the value from two bars before and so on. This is mostly used for price curves and their derivatives. For instance, we could use a series to take the current price of an asset, compare it with the price from 1 bar before, and do some other things dependent on past prices. The series is the ideal construct for such price calculations.

    The current value of a series can be used by adding a [0] to the series name; for the value from one bar before add a [1], for two bars before add a [2] and so on. So, in Alice's code Price[0] would be the current value of the Price series, and Price[1] the value from 1 hour ago. Many trade platform languages - for instance, EasyLanguage - support series this way; usually indicator, statistics, and financial functions all use series instead of single variables. We'll encounter series very often in trade scripts and will become familiar with them.

    The series() function can be used to convert a single variable to a series. The variable or value for filling the series is normally passed to that function. However, we're not using a variable here, but the return value of a function call. var *Price = series(price()); means: define a var series with the name "Price" and fill it with the return value of the price() function. We've learned in the last programming lesson how to 'nest' function calls this way, passing the return values of functions as parameters to other functions.

    The price() function returns the mean price of the selected asset at the current bar. There are also priceOpen(), priceClose(), priceHigh() and priceLow() functions that return the open, close, maximum and minimum price of the bar; however, the mean price is usually the best for trend trading strategies. It's averaged over all prices inside the bar and thus generates a smoother price curve.

    var *Trend = series(LowPass(Price,1000));

    The next line defines a series named "Trend" and fills it with the return value from the LowPass function. As you probably guessed, this function is Alice's second order lowpass filter. Its parameters are the previously defined Price series and a time period, which Alice has set to 1000 bars. 1000 bars are about 2 months (1 week = 24*5 = 120 hours). Thus the lowpass filter attenuates all the wiggles and jaggies of the Price series that are shorter than 2 months, but it does not affect the trend or long-term cycles above two months. It has a similar smoothing effect as a Moving Average function, 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 limit:

    Stop = 2*ATR(100);

    Stop is a predefined variable that Zorro knows already, so we don't have to define it. It's the maximum allowed loss of the trade; the position is sold immediately when it lost more than the given value. The limit here is given by 2*ATR(100). The ATR function is a standard indicator. It returns the Average Price Range - meaning the average height of a candle - within a certain number of bars, here the last 100 bars. So the position is sold when the loss exceeds two times the average candle height of the last 100 bars. By setting Stop not at a fixed value, but at a value dependent on the fluctuation of the price, Alice adapts the stop loss to the market situation. When the price fluctuates a lot, higher losses are allowed. Otherwise trades would be stopped out too early when the price jumps down just for a moment.

    A stop loss should be used in all trade strategies. It not only limits losses, it also allows Zorro's trade engine to better calculate the risk per trade and generate a more accurate performance analysis.

    The next lines are the core of Alice's strategy:

    else if(peak(Trend)) 
    The valley function is a boolean function; it returns either true or false. It returns true when the series just had a downwards peak. The peak function returns true when it just had an upwards peak. When the if(..) condition becomes true, a long or short trade with the selected asset is entered with a enterLong or enterShort command. If a trade was already open in the opposite direction, it is automatically closed. Note how we combined the else of the first if with a second if; the second if() statement is only executed when the first one was not.

    Let's have a look into an example trade triggered by this command:


    The red line in the chart above is the Trend series. You can see that it has a peak at the end of September 2008, so the peak(Trend) function returned true and the enterShort function was called. The tiny green dot is the moment where a short trade was entered. The Trend series continues downwards all the way until November 23, when a valley was reached. A long trade (not shown in this chart) was now 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.

    In the next lesson we'll learn how to backtest such a strategy and how to judge its profitability. There were a lot new concepts in this lesson, so please ask here if something is unclear or could be better explained.
  6. Pardo's book reads like this, but I don't think this post is plagiarism.

    In Pardo's book, they have a dialogue between a trader and a programmer. The trader is all like:

  7. jcl


    Obviously, a strategy is worthless as long as you don't know how it will perform. For this you need to run a backtest over at least 4 years, better 6 years. Start up Zorro, select the [Workshop4] script and the [EUR/USD] asset, leave the [Period] slider at 60 minutes, then click [Test]:


    You might get a slightly different result when you downloaded the patch, as the default slippage was changed to better reflect worst case situations. By default, the simulation runs over the last 4 full years, i.e. from 2008 until 2011. The strategy achieves an annual profit of 94% - that's the average profit per year divided by the sum of maximum drawdown and maximum margin. The monthly income (MI in the window) by this strategy is $115 - quite modest, but you traded with the minimum margin. $1465 capital is required for keeping a safe distance from a margin call. The figures are based on the investment of 1 lot per trade, usually equivalent to a $50 margin, the lowest possible trade size for a mini lot account.

    Although 94% look a lot better than the usual 4% of a savings account, Alice is not too excited about the result. The Sharpe Ratio (SR) of 0.88 tells her that this result comes at a risk. The Sharpe ratio is the mean annual profit divided by its standard deviation. Sharpe ratios below 1 indicate that the gains fluctuate a lot - there might be years when the strategy achieves a lower profit, or even a loss. This is confirmed by the profit curve that Alice gets by clicking [Result]:


    In the image that pops up in the chart viewer (if not, you need to install the patch from the Zorro forum), you can see a blue area, a black curve, and some green lines and red dots attached to the curve. The black curve is the price of the selected asset - the EUR/USD, i.e. the price of 1 EUR in US$. 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 immediately that there are far more red than green dots - 88% of the trades are lost, only 12% are won. However, the long-term trades all have green lines. So we have a lot of small losses, but several large wins. This is the typical result of a trend following strategy.

    The most interesting part of the chart is the blue curve that indicates our equity. We can see that it's going up from zero in 2008 to $5500 (5.5K) at the end of 2011. This is a good thing. However the equity curve is quite shaky. The winning years are 2008 and 2010; in 2009 and 2011 we had a loss. This shaky behavior is reflected in the low Sharpe Ratio (SR) and the high Ulcer Index (UI). It would require strong nerves to trade such a strategy; there are long periods during which the strategy is losing money. Still, Alice thinks that this strategy is good enough for that lousy $5,000 programming fee, and Bob got what he wanted. We'll learn in the next workshop how to write better strategies.

    We can experiment a little by replacing the lowpass filter with other methods. When replaced with an traditional Exponential Moving Average (EMA) indicator, we had to use a shorter time period of 250 bars for getting about the same lag as a lowpass filter with 1000 bars. The second line in the code would then look like this:

    var *Trend = series(EMA(Price,250));

    and the system would be unprofitable, as an EMA with this short time period is too "jittery" for profitable trend trading with this method. Using lowpass filters instead of moving averages almost always improves the strategy performance, and often makes the difference between a winning and a losing system.

    What have we learned so far?

    ► A strategy script contains a run function that is called once per bar.

    ► A series is a variable with a history.

    ► A lowpass filter removes the jaggies from a price curve without much lag penalty. It is superior to traditional moving averages.

    ► The valley and peak functions can be used to buy or sell at the turning points of a curve.

    ► A stop loss determines the trade risk.

    Please post if something didn't work or is unclear.
  8. jcl


    BTW, if someone wants to try this strategy in other platforms, here's the C source code of the LowPass, peak, and valley functions:

    var smoothF(int period) { return 2./(period+1); }
    var LowPass(var *Data,int Period)
    	var* LP = series(*Data,3);
    	var a = smoothF(Period);
    	var a2 = a*a;
    	return LP[0] = (a-0.25*a2)*Data[0]
    		+ 0.5*a2*Data[1]
    		- (a-0.75*a2)*Data[2]
    		+ 2*(1.-a)*LP[1]
    		- (1.-a)*(1.-a)*LP[2];
    BOOL peak(var* a) { return a[2] < a[1] && a[1] > a[0]; }
    BOOL valley(var* a) { return a[2] > a[1] && a[1] < a[0]; }
  9. What are initial values for LP[1] and LP[2]? Zeros?
  10. jcl


    No, the asset price - they are filled from *Data.
    #10     Sep 15, 2012