Category Archives: PortfolioTrader

Should you use a profit taking algorithm in your Trend Following system?

If letting profits run is key to the success of a trend following approach, is there a way to take profit without diminishing returns?

Most trend following approaches win less than 40% of the time.   So, the big profitable trades are what saves the day for this type of trading approach.  However, it is pure pain to simply sit there and watch a large profit erode, just because the criteria to exit the trade takes many days to be met.

Three methods to take a profit on a Trend Following algorithm

  1.  Simple profit objective – take a profit at a multiple of market risk.
  2.  Trail a stop (% of ATR) after a profit level (% of ATR) is achieved.
  3. Trail a stop (Donchian Channel) after a profit level (% of ATR) is achieved.

Use an input switch to determine which exit to incorporate

Inputs: initCapital(200000),rskAmt(.02),
useMoneyManagement(False),exitLen(13),
maxTradeLoss$(2500),
// the following allows the user to pick
// which exit to use
// 1: pure profit objective
// exit1ProfATRMult allows use to select
// amount of profit in terms of ATR
// 2: trailing stop 1 - the user can choose
// the treshhold amount in terms of ATR
// to be reached before trailing begins
// 3: trailing stop 2 - the user can chose
// the threshold amount in terms of ATR
// to be reached before tailing begins
whichExit(1),
exit1ProfATRMult(3),
exit2ThreshATRMult(2),exit2TrailATRMult(1),
exit3ThreshATRMult(2),exit3ChanDays(5);
Exit switch and the parameters needed for each switch.

The switch determines which exit to use later in the code.  Using inputs to allow the user to change via the interface also allows us to use an optimizer to search for the best combination of inputs.  I used MultiCharts Portfolio Trader to optimize across a basket of 21 diverse markets.  Here are the values I used for each exit switch.

MR = Market risk was defined as 2 X avgTrueRange(15).

  • Pure profit objective -Multiple from 2 to 10 in increments of 0.25.  Take profit at entryPrice + or – Profit Multiple X MR
  • Trailing stop using MR – Profit Thresh Multiple from 2 to 4 in increments of 0.1.  Trailing Stop Multiple from 1 to 4 in increments of 0.1.
  • Trailing stop using MR and Donchian Channel – Profit Thresh Multiple from 2 to 4 in increments of 0.1.  Donchian length from 3 to 10 days.

Complete strategy code incorporating exit switch.  This code is from Michael Covel’s 2005 Trend Following book (Covel, Michael. Trend Following: How Great Traders Make Millions in Up or Down Markets. FT Press, 2005.)  This strategy is highlighted in my latest installment in my Easing into EasyLanguage series – Trend Following edition.


vars:buyLevel(0),shortLevel(0),longExit(0),shortExit(0);

Inputs: initCapital(200000),rskAmt(.02),
useMoneyManagement(False),exitLen(13),
maxTradeLoss$(2500),whichExit(1),
exit1ProfATRMult(3),
exit2ThreshATRMult(2),exit2TrailATRMult(1),
exit3ThreshATRMult(2),exit3ChanDays(5);

Vars: marketRisk(0), workingCapital(0),
marketRisk1(0),marketRisk2(0),
numContracts1(0),numContracts2(0);

//Reinvest profits? - uncomment the first line and comment out the second
//workingCapital = Portfolio_Equity-Portfolio_OpenPositionProfit;
workingCapital = initCapital;


buyLevel = highest(High,89) + minMove/priceScale;
shortLevel = lowest(Low,89) - minMove/priceScale;
longExit = lowest(Low,exitLen) - minMove/priceScale;
shortExit = highest(High,exitLen) + minMove/priceScale;

marketRisk = avgTrueRange(15)*2*BigPointValue;
marketRisk1 =(buyLevel - longExit)*BigPointValue;
marketRisk2 =(shortExit - shortLevel)*BigPointValue;
marketRisk1 = minList(marketRisk,marketRisk1);
marketRisk2 = minList(marketRisk,marketRisk2);

numContracts1 = (workingCapital * rskAmt) /marketRisk1;
numContracts2 = (workingCapital * rskAmt) /marketRisk2;

if not(useMoneyManagement) then
begin
numContracts1 = 1;
numContracts2 =1;
end;

numContracts1 = maxList(numContracts1,intPortion(numContracts1)); {Round down to the nearest whole number}
numContracts2 = MaxList(numContracts2,intPortion(numContracts1));


if c < buyLevel then buy numContracts1 contracts next bar at buyLevel stop;
if c > shortLevel then Sellshort numContracts2 contracts next bar at shortLevel stop;

buytocover next bar at shortExit stop;
Sell next bar at longExit stop;

vars: marketRiskPoints(0);
marketRiskPoints = marketRisk/bigPointValue;
if marketPosition = 1 then
begin
if whichExit = 1 then
sell("Lxit-1") next bar at entryPrice + exit1ProfATRMult * marketRiskPoints limit;
if whichExit = 2 then
if maxcontractprofit > (exit2ThreshATRMult * marketRiskPoints ) * bigPointValue then
sell("Lxit-2") next bar at entryPrice + maxContractProfit/bigPointValue - exit2TrailATRMult*marketRiskPoints stop;
if whichExit = 3 then
if maxcontractprofit > (exit3ThreshATRMult * marketRiskPoints ) * bigPointValue then
sell("Lxit-3") next bar at lowest(l,exit3ChanDays) stop;
end;

if marketPosition = -1 then
begin
if whichExit = 1 then
buyToCover("Sxit-1") next bar at entryPrice - exit1ProfATRMult * marketRiskPoints limit;
if whichExit = 2 then
if maxcontractprofit > (exit2ThreshATRMult * marketRiskPoints ) * bigPointValue then
buyToCover("Sxit-2") next bar at entryPrice - maxContractProfit/bigPointValue + exit2TrailATRMult*marketRiskPoints stop;
if whichExit = 3 then
if maxcontractprofit > (exit3ThreshATRMult * marketRiskPoints ) * bigPointValue then
buyToCover("Sxit-3") next bar at highest(h,exit3ChanDays) stop;
end;

setStopLoss(maxTradeLoss$);

Here’s the fun code from the complete listing.

vars: marketRiskPoints(0);
marketRiskPoints = marketRisk/bigPointValue;
if marketPosition = 1 then
begin
if whichExit = 1 then
sell("Lxit-1") next bar at entryPrice + exit1ProfATRMult * marketRiskPoints limit;
if whichExit = 2 then
if maxContractProfit > (exit2ThreshATRMult * marketRiskPoints ) * bigPointValue then
sell("Lxit-2") next bar at entryPrice + maxContractProfit/bigPointValue - exit2TrailATRMult*marketRiskPoints stop;
if whichExit = 3 then
if maxContractProfit > (exit3ThreshATRMult * marketRiskPoints ) * bigPointValue then
sell("Lxit-3") next bar at lowest(l,exit3ChanDays) stop;
end;

if marketPosition = -1 then
begin
if whichExit = 1 then
buyToCover("Sxit-1") next bar at entryPrice - exit1ProfATRMult * marketRiskPoints limit;
if whichExit = 2 then
if maxContractProfit > (exit2ThreshATRMult * marketRiskPoints ) * bigPointValue then
buyToCover("Sxit-2") next bar at entryPrice - maxContractProfit/bigPointValue + exit2TrailATRMult*marketRiskPoints stop;
if whichExit = 3 then
if maxContractProfit > (exit3ThreshATRMult * marketRiskPoints ) * bigPointValue then
buyToCover("Sxit-3") next bar at highest(h,exit3ChanDays) stop;
end;

The first exit is rather simple – just get out on a limit order at a nice profit level.  The second and third exit mechanisms are a little more complicated.  The key variable in the code is the maxContractProfit keyword.  This value stores the highest level, from a long side perspective, reached during the life of the trade.  If max profit exceeds the exit2ThreshATRMult, then trail the apex by exit2TrailATRMult.  Let’s take a look at the math from a long side trade.

if maxContractProfit > (exit2ThreshATRMult * marketRiskPoints ) * bigPointValue

Since maxContractProfit is in dollar you must convert the exit2ThreshATRMult X marketRiskPoints into dollars as well.  If you review the full code listing you will see that I convert the dollar value, marketRisk, into points and store the value in marketRiskPoints.  The conversion to dollars is accomplished by multiplying the product by bigPointValue.

sell("Lxit-2") next bar at
entryPrice + maxContractProfit / bigPointValue - exit2TrailATRMult * marketRiskPoints stop;

I know this looks complicated, so let’s break it down.  Once I exceed a certain profit level, I calculate a trailing stop at the entryPrice plus the apex in price during the trade (maxContractProfit / bigPointValue) minus the exit2TrailATRMult X marketRiskPoints. If the price of the market keeps rising, so will the trailing stop.  That last statement is not necessarily true, since the trailing stop is based on market volatility in terms of the ATR.  If the market rises a slight amount, and the ATR increases more dramatically, then the trailing stop could actually move down.  This might be what you want.  Give the market more room in a noisier market.  What could you do to ratchet this stop?  Mind your dollars and your points in your calculations.

The third exit uses the same profit trigger, but simply installs an exit based on a shorter term Donchian channel.  This is a trailing stop too, but it utilizes a chart point to help define the exit price.

Results of the three exits

Exit 1 – Pure Profit Objective

Take a profit on a limit order once profit reaches a multiple of market risk aka 2 X ATR(15).

Pure profit object. Profit in terms of ATR or perceived market risk.

The profit objective that proved to be the best was using a multiple of 7.  A multiple of 10 basically negates the profit objective.   With this system several profit objective multiples seemed to work.

Exit – 2 – Profit Threshold and Trailing Stop in terms of ATR or market risk

Trail a stop a multiple of ATR after a multiple of ATR in profit is reached.

Trailing Stop using ATR
3-D view of parameters
3D view of parameter pairs

This strategy liked 3 multiples of ATR of profit before trailing and applying a multiple of 1.3 ATR as a stop.

Like I said in the video, watch out for 1.3 as you trailing amount multiple as it seems to be on a mountain ridge.

Exit – 3 – Profit Threshold in terms of ATR or market risk and a Donchain Channel trailing stop

Trail a stop using a Donchian Channel after a multiple of ATR in profit is reached.  Here was a profit level is reached, incorporate a tailing stop at the lowest low or the highest high of N days back.

Using Donchian Channel as trailing stop.
3-D view of parameters
3D view of parameters for Exit 3.

Conclusion

The core strategy is just an 89-day Donchian Channel for entry and a 13-Day Donchian Channel for exit.  The existing exit is a trailing exit and after I wrote this lengthy post, I started to think that a different strategy might be more appropriate.  However, as you can see from the contour charts, using a trailing stop that is closer than a 13-day Donchian might be more productive.   From this analysis you would be led to believe the ATR based profit and exit triggers (Exit #2) is superior.  But this may not be the case for all strategies.  I will leave this up to you to decide.  Here is the benchmark performance analysis with just the core logic.

Core logic results.

If you like this type of explanation and code, make sure you check at my latest book at amazon.com.  Easing into EasyLanguage – Trend Following Edition.

Prune Your Trend Following Algorithm

Multiple trading decisions based on “logic” may not add to the bottom line

In this post, I will present a trend following system that uses four exit techniques.  These techniques are based on experience and also logic.  The problem with using multiple exit techniques is that it is difficult to see the synergy that is generated from all the moving parts.  Pruning your algorithm may help cut down on invisible redundancy and opportunities to over curve fit.  The trading strategy I will be presenting will use a very popular entry technique overlaid with trade risk compression.

Entry logic

Long:

Criteria #1:  Penetration of the closing price above an 85 day (closing prices) and 1.5X standard deviation-based Bollinger Band.

Criteria #2:  The mid-band or moving average must be increasing for the past three consecutive days.

Criteria #3: The trade risk (1.5X standard deviation) must be less than 3 X average true range for the past twenty days and also must be less than $4,500.

Risk is initially defined by the standard deviation of the market but is then compared to $4,500. If the trade risk is less than $4,500, then a trade is entered. I am allowing the market movement to define risk, but I am putting a ceiling on it if necessary.

Short:

Criteria #1:  Penetration of the closing price below an 85 day (closing prices) and 1.5X standard deviation-based Bollinger Band.

Criteria #2:  The mid-band or moving average must be decreasing for the past three consecutive days.

Criteria #3:  Same as criteria #3 on the long side

Exit Logic

Exit #1:  Like any Bollinger Band strategy, the mid band or moving average is the initial exit point.  This exit must be included in this particular strategy, because it allows exits at profitable levels and works synergistically with the entry technique.

Exit #2:  Fixed $ stop loss ($3,000)

Exit #3:  The mid-band must be decreasing for three consecutive days and today’s close must be below the entry price.

Exit #4:  Todays true range must be greater than 3X average true range for the past twenty days, and today’s close is below yesterday’s, and yesterday’s close must be below the prior days.

Here is the logic of exits #2 through exit #4.  With longer term trend following system, risk can increase quickly during a trade and capping the maximum loss to $3,000 can help in extreme situations.  If the mid-band starts to move down for three consecutive days and the trade is underwater, then the trade probably should be aborted.  If you have a very wide bar and the market has closed twice against the trade, there is a good chance the trade should be aborted.

Short exits use the same logic but in reverse.  The close must close below the midband, or a $3,000 maximum loss, or three bars where each moving average is greater than the one before, or a wide bar and two consecutive up closes.

Here is the logic in PowerLanguage/EasyLanguage that includes the which exit seletor.

[LegacyColorValue = true]; 
Inputs: maxEntryRisk$(4500),maxNATRLossMult(3),maxTradeLoss$(3000),
indicLen(85),numStdDevs(1.5),highVolMult(3),whichExit(7);

Vars: upperBand(0), lowerBand(0),slopeUp(False),slopeDn(False),
largeAtr(0),sma(0),
initialRisk(0),tradeRisk(0),
longLoss(0),shortLoss(0),permString("");

upperBand = bollingerBand(close,indicLen,numStdDevs);
lowerBand = bollingerBand(close,indicLen,-numStdDevs);
largeATR = highVolMult*(AvgTrueRange(20));

sma = average(close,indicLen);

slopeUp = sma>sma[1] and sma[1]>sma[2] and sma[2]>sma[3];
slopeDn = sma<sma[1] and sma[1]<sma[2] and sma[2]<sma[3];

initialRisk = AvgTrueRange(20);
largeATR = highVolMult * initialRisk;
tradeRisk = (upperBand - sma);
// 3 objects in our permutations
// exit 1, exit 2, exit 3
// perm # exit #
// 1 1
// 2 1,2
// 3 1,3
// 4 2
// 5 2,3
// 6 3
// 7 1,2,3

if whichExit = 1 then permString = "1";
if whichExit = 2 then permString = "1,2";
if whichExit = 3 then permString = "1,3";
if whichExit = 4 then permString = "2";
if whichExit = 5 then permString = "2,3";
if whichExit = 6 then permString = "3";
if whichExit = 7 then permString = "1,2,3";



{Long Entry:}
If (MarketPosition = 0) and
Close crosses above upperBand and slopeUp and
(tradeRisk < initialRisk*maxNATRLossMult and tradeRisk<maxEntryRisk$/bigPointValue) then
begin
Buy ("LE") Next Bar at Market;
End;


{Short Entry:}

If (MarketPosition = 0) and slopeDn and
Close crosses below lowerBand and
(tradeRisk < initialRisk*maxNATRLossMult and tradeRisk<maxEntryRisk$/bigPointValue) then
begin
Sell Short ("SE") Next Bar at Market;
End;


{Long Exits:}

if marketPosition = 1 Then
Begin
longLoss = initialRisk * maxNATRLossMult;
longLoss = minList(longLoss,maxTradeLoss$/bigPointValue);

If Close < sma then
Sell ("LX Stop") Next Bar at Market;;

if inStr(permString,"1") > 0 then
sell("LX MaxL") next bar at entryPrice - longLoss stop;

if inStr(permString,"2") > 0 then
If sma < sma[1] and sma[1] < sma[2] and sma[2] < sma[3] and close < entryPrice then
Sell ("LX MA") Next Bar at Market;
if inStr(permString,"3") > 0 then
If TrueRange > largeATR and close < close[1] and close[1] < close[2] then
Sell ("LX ATR") Next Bar at Market;
end;

{Short Exit:}

If (MarketPosition = -1) Then
Begin

shortLoss = initialRisk * maxNATRLossMult;
shortLoss = minList(shortLoss,maxTradeLoss$/bigPointValue);
if Close > sma then
Buy to Cover ("SX Stop") Next Bar at Market;

if inStr(permString,"1") > 0 then
buyToCover("SX MaxL") next bar at entryPrice + shortLoss stop;

if inStr(permString,"2") > 0 then
If sma > sma[1] and sma[1] > sma[2] and sma[2] > sma[3] and close > entryPrice then
Buy to Cover ("SX MA") Next Bar at Market;
if inStr(permString,"3") > 0 then
If TrueRange > largeAtr and close > close[1] and close[1] > close[2] then
Buy to Cover ("SX ATR") Next Bar at Market;
end;
Trend following with exit selector

Please note that I modified the code from my original by forcing the close to cross above or below the Bollinger Bands.  There is a slight chance that one of the exits could get you out of a trade outside of the bands, and this could potentially cause and automatic re-entry in the same direction at the same price.  Forcing a crossing, makes sure the market is currently within the bands’ boundaries.

This code has an input that will allow the user to select which combination of exits to use.

Since we have three exits, and we want to evaluate all the combinations of each exit separately, taken two of the exits and finally all the exits, we will need to rely on a combinatorial table.    In long form, here are the combinations:

3 objects in our combinations of exit 1, exit 2, exit 3

  • one  – 1
  • two  – 1,2
  • three  –  1,3
  • four –  2
  • five  – 2,3
  • six –  3
  • seven  –  1,2,3

There are a total of seven different combinations. Given the small set, we can effectively hard-code this using string manipulation to create a combinatorial table. For larger sets, you may find my post on the Pattern Smasher beneficial. A robust programming language like Easy/PowerLanguage offers extensive libraries for string manipulation. The inStr string function, for instance, identifies the starting position of a substring within a larger string. When keyed to the whichExit input, I can dynamically recreate the various combinations using string values.

  1. if whichExit = 1 then permString = “1”
  2. if whichExit = 2 then permString= “1,2”
  3. if whichExit = 3 then permString = “1,2,3”
  4.  etc…

As I optimize from one to seven, permString will dynamically change its value, representing different rows in the table. For my exit logic, I simply check if the enumerated string value corresponding to each exit is present within the string.

	if inStr(permString,"1") > 0 then
sell("LX MaxL") next bar at entryPrice - longLoss stop;
if inStr(permString,"2") > 0 then
If sma < sma[1] and sma[1] < sma[2] and sma[2] < sma[3] and close < entryPrice then
Sell ("LX MA") Next Bar at Market;
if inStr(permString,"3") > 0 then
If TrueRange > largeATR and close < close[1] and close[1] < close[2] then
Sell ("LX ATR") Next Bar at Market;
Using inStr to see if the current whichExit input applies

When permString = “1,2,3” then all exits are used.  If permString = “1,2”, then only the first two exits are utilized.  Now all we need to do is optimize whichExit from 1 to 7.  Let’s see what happens:

Combination of all three exits

The best combination of exits was “3”.  Remember 3 is the permString  that = “1,3” – this combination includes the money management loss exit, and the wide bar against position exit.  It only slightly improved overall profitability instead of using all the exits – combo #7.  In reality, just using the max loss stop wouldn’t be a bad way to go either.  Occam uses his razor to shave away unnecessary complexities again!

If you like this code, you should check out the Summer Special at my digital store. I showcase over ten more trend-following algorithms with different entry and exit logic constructs.  These other algorithms are derived from the best Trend Following “Masters” of the twentieth century.  IMHO!

Here is a video you can watch that goes over the core of this trading strategy.