Simplify my system - remove trailing stop loop

I have a long only system that uses a trailing stop loop to implement the sell signals. All buys and sells are next day open (Monday's open) i.e. SetTradeDelays(1,1,1,1). It is a weekly system.

The sell rules are pretty straightforward:

  1. If the Close is below an ATR based trailing stop, sell.
  2. If the Close is above the Buy price (Monday's open), sell. This can mean sell on the buy bar, i.e. buy on Monday, sell signal on Friday, sell order on Monday's open.
  3. If X bars have passed since the Buy, sell (stale position).

The rules execute in that order in the trailing stop loop.

I'm trying to simplify the code to eliminate the trailing stop loop: 1) simple is better, 2) Tomasz advises using array processing when possible, as it is much faster than loops. This system gets good results, but the backtests are slow (due to my code).

In a way, I'm using the trailing stop loop not to be "fancy", but because I need to improve my Amibroker skills with respect to array processing.

I think #1 can be a chandelier ApplyStop.
I'm stumped on #2. I'm not sure how to capture the BuyPrice (Monday's open) as a scalar, or if I even need to do so.
I think #3 can be done using BarsSince(Buy), except I think this is failing since there can be multiple buy signals for the same symbol each week.

Here is what I've tried, but it gives much poorer results than the trailing stop loop.

// Trailing Stop
AtrTrailStopMult = 1;
AtrTrailStopPeriod = 2;
RiskPerShare = AtrTrailStopMult * Ref(ATR(AtrTrailStopPeriod),-1);   // ATR from the signal bar  

// Stops
// Sells

// Sell if Close > Buy
SellRule1 = IIf(BarsSince(Buy) > 0,IIf(Close > Open,1,0),0);  

// Wrong, this needs to be Close > BuyPrice, which *could* be this bar's Open
// but could also be the Open several bars ago

// Sell if Bars Since Buy > BarsSinceBuyCutoff
BarsSinceBuyCutoff = 5;
SellRule2 = IIf(BarsSince(Buy) > BarsSinceBuyCutoff,1,0);

// Wrong, there could be multiple buy signals for this symbol, i.e. several weeks in a row

If the best approach is the trailing stop loop just confirm that, as it's working and gives good results. I can live with the slow backtest, I just didn't want my lack of Amibroker skills to be the reason for that.

Also, I assume I can't plot the trailing stop from ApplyStop on a chart? (which I can do with the trailing stop loop).

Using a Trailing Stop For Loop gives you more control.

Fair point but we can't review your For Loop code for efficiency - you didn't post it.

Of course you can. Plenty of examples of that already on this forum.

1 Like
1 Like

Thanks @TrendSurfer, much appreciated.

Yeah, I actually do like the trailing stop loop, but was told by someone much more experienced than me (who is not a coder) that I should simplify my code. Plus I can walk the trailing stop loop through the debugger.

Here's my TSL:

TrailValue = StopLoss;  
TrailStopPrice = 0;  
TrailStopSignal = 0;  
TrailStopReason = Null;  
for (i=0; i < BarCount; i++)  
    // Buy Signal is on today's close, Buy on Next Bar Order (either next day or next Monday depending on periodicity)  
    if (Buy[i] AND NOT InBuy) {  
        // debugging  
        str="TRG: BarNum:\t%3.0f\t\tTrigDate: %s\t\t\tBuyPrice: %3.2f\tClosePrice: %3.2f\tTrailingStop: %3.2f\n";  
        // since we buy on the next bar after the buy signal  
        // return to the top of the loop and iterate to the next bar  
    if (InBuy) {  
        // Set the static variable _BuyPrice to the Open of the Buy bar  
        if (NOT IsBuyPrice) {  
        // raise the trailing stop  
        if (TrailStopPrice[i]==0) {  
            TrailStopPrice[i] = Max(TrailValue[i], TrailStopPrice[i-1]);  
        // debugging  
        str="BUY: BarNum:\t%3.0f\t\tBuyDate: %s\t\t\tBuyPrice: %3.2f\tClosePrice: %3.2f\tTrailingStop: %3.2f\n";  
        // if the trailing stop is breached stop processing  
        if (Close[i] < TrailStopPrice[i]) {  
            // debugging  
            str="TRAIL: BarNum:\t%3.0f\t\tSellDate: %s\t\t\tBuyPrice: %3.2f\tClosePrice: %3.2f\tTrailingStop: %3.2f %s\n";  
            if (Close[i] > _BuyPrice) { ind="<<< GAIN"; } else { ind="<<<LOSS"; }  
            TrailStopReason[i]=1;   // TRAILING STOP  
        // If the Close is above the Buy Price exit  
        if (Close[i] > _BuyPrice) {  
            // debugging  
            str="CABP: BarNum:\t%3.0f\t\tSellDate: %s\t\t\tBuyPrice: %3.2f\tClosePrice: %3.2f\tTrailingStop: %3.2f %s\n";  
            if (Close[i] > _BuyPrice) { ind="<<< GAIN"; } else { ind="<<<LOSS"; }  
            TrailStopReason[i]=2;   // CLOSE ABOVE BUY PRICE  
        // Exit after X bars below the Buy Price  
        if (BarsSinceBuy > BarsSinceBuyCutoff) {  
            // debugging  
            str="STALE: BarNum:\t%3.0f\t\tSellDate: %s\t\t\tBuyPrice: %3.2f\tClosePrice: %3.2f\tTrailingStop: %3.2f %s\n";  
            if (Close[i] > _BuyPrice) { ind="<<< GAIN"; } else { ind="<<<LOSS"; }  
            TrailStopReason[i]=3;   // STALE POSITION  
// Standard Default Sell Filters  
SellRuleA = 0;  // Default is to not Sell  
SellRuleB = DelistedSecurity;  // Norgate Sell Signal  
// Strategy's Sell Filters  
SellRule1 = TrailStopSignal;  
Sell = SellRuleA  
    OR SellRuleB  
    OR SellRule1  

Then later in the chart section:

//InTrade = Flip(Buy,Sell);  
InTrade = Flip(Ref(Buy,-1),Ref(Sell,-1));  
// Add watermark so we know we have the correct chart with the correct analysis  
GfxSelectFont("Tahoma", 24, weight=300, italic=true, underline=false, orientation=0);  
GfxSetTextAlign(6);     // 6 = center, 0 = left, 2 = right  
GfxSetBkMode(1);        // transparent  
GfxTextOut(SystemNameVer, Status("pxwidth")/2.2, Status("pxheight")/2.5);  
// Trailing Stop Reason message  
stopMessage = "";  
stopMessage = StrExtract(stopMessageString,idx);  
SetChartOptions(1, chartShowArrows | chartShowDates, chartGridMiddle, 0, 0, 0);  
_N(Title = StrFormat("{{NAME}} - %s - {{INTERVAL}} - %s\nOpen %g, Hi %g, Lo %g, Close %g (%.1f%%)\n{{VALUES}} %s",  
FullName(),DateNumToDateFull(DateNum(),SelectedValue(BarIndex())), O, H, L, C, SelectedValue(ROC(C, 1)), stopMessage ));  
Plot(Close, "Close", ParamColor("Color", colorDefault), styleNoTitle | ParamStyle("Style") | GetPriceStyle());  
// Show Trailing Stop Price and Reason if in a trade  
Plot(IIf(InTrade,TrailStopPrice,Null),"Trailing Stop Price", colorRed, styleLine | styleThick);  
Plot(IIf(InTrade,TrailStopReason,Null),"Trailing Stop Reason", colorWhite, styleNoDraw);  
// Show Buy and Sell Arrows  
// Show Buy and Sell price on the chart on the Buy and Sell bar (not the Trigger Bar)  
textdist = 3*ATR(10);  
for( i = 1; i < BarCount; i++)  
    if (Buy[i - 1])  PlotText("Buy\n@ " +  Prec (Open[i],3), i, L[i] - textdist[i], colorWhite);  // Displays white buy price (opening price) under the white box (buy bar)  
    if (Sell[i - 1]) PlotText("Sell\n@ " + Prec (Open[i],3), i, H[i] + textdist[i], colorWhite);  // Displays yellow sell price (opening price) above the yellow circle (sell bar)  

DateNumToStr is in an include file:


function DateNumToYYYYMMDDD(nDate,index)
	dt = DateTimeConvert(2,nDate);
	ds = DateTimeFormat("%Y-%m-%d",dt[index]);
	return ds;

function DateNumToStr(nDate,index)
	// alias this to your favourite, stock standard DateNumToXXX function
	// then use this alias throughout your code
	// you could then change this one alias to enable a system wide change
	// for example, sharing your code with someone in a different region
	return DateNumToYYYYMMDDD(nDate,index);

Based on what you've said I'll keep the trailing stop loop. I've gone over the code with a fine toothed comb. Of course there could be a bug, that's why they're called bugs, but AFAIK the code is clean and the system is good. Assuming it is not curve fit, etc...things Amibroker has no control over.

My laptop is about 4 years old: Lenovo X1 Carbon, i7-6600U CPU @ 2.60Ghz, 2808 Mhz, 2 Core(s), 4 Logical Processors, 16GB RAM. With 2135 symbols in this watchlist my backtest is taking 1-1.5 minutes. Ok when I'm backtesting, not so good when I'm optimizing.

I hope to buy a better spec'd server machine in the near future...AMD Threadripper 32 core to start with. I'm not sure if Amibroker takes advantage of high end GPU's? Regardless, that machine will hopefully speed things up.

When posting code for others to analyse you should post your full code and in the one block!

As such from the parts you have posted (to begin with) you could:

  • Process testing/debugging lines of code only when required, eg.
applyTesting = 1;

if (applyTesting)
	// testing/debugging lines...
  • Use 'Status("ActionEx") == action...' for appropriate code lines, eg 'actionIndicator' for Gfx lines
  • Use 'FirstVisibleValue()'/'LastVisibleValue()' for appropriate code lines, eg 'PlotText' lines

This topic was automatically closed 100 days after the last reply. New replies are no longer allowed.