How do I backtest a script that only sets signals on the last bar?

This very simple script generates Buy/Sell signals for the very last bar only. It is designed to run on live charts.

How do I backtest this type of scripts?

The backtester runs it only once and reports the signals of the last bar only even if I set range to "All quotes". I need to make backtester iterate through all bars, and evaluate this script as if each of these bars was the last one. Is there a way to do this?

The script loads all essentials variables in static vars and continues on the next cycle.

It makes a BUY if the last bar is bullish. Holds the trade for 4 bars and then sells. It keeps only one position open. (Of course this is only an example. Please do not try to optimise this and make it into a formula. this is not the point.)

You can run it with bar replay to see it working.

SetBarsRequired( sbrAll, sbrAll );

bi = BarCount - 1;

Buy = Nz( StaticVarGet( "buy" ) );
Sell = Nz( StaticVarGet( "sell" ) );
long_bar = Nz( StaticVarGet( "long_bar" ) );
prev_barcount = Nz( StaticVarGet( "prev_barcount" ) );

// make it more elegant
if( long_bar > prev_barcount ) // we have garbage from a previous run.  We need to reset the static vars
{
    StaticVarRemove( "*" );
    Buy = Nz( StaticVarGet( "buy" ) );
    Sell = Nz( StaticVarGet( "sell" ) );
    long_bar = Nz( StaticVarGet( "long_bar" ) );
    prev_barcount = Nz( StaticVarGet( "prev_barcount" ) );
}

if( prev_barcount  != Barcount ) // we have a new bar
{
// The new bars of the static arrays are not blank.  They have the same values like the previous bar.
// So we have to detect when new bars appear, and initiallize them. 
    Buy[bi] = false;
    sell[bi] = false;
    printf( "We have a new bar !!!  \n" );
}

if( long_bar == 0 ) // no open position
{
// Now set the signal of the last bar, based on our rules (every BULL bar is a BUY signal)
    if( O[bi] < C[bi] )
    {
        Buy[bi] = True;
        Long_bar = bi; // always the last bar
    }
}
else  // we have open position
    if( long_bar < ( BarCount - 4 ) ) // hold for 4 bars
    {
        sell[bi] = True;
        long_bar = 0;
    }


/* Plot Buy/Sell Signal Arrows */
shape = Buy * shapeUpArrow;
PlotShapes( shape, colorGreen, 0, Low );
shape = Sell * shapeDownArrow;
PlotShapes( shape, colorRed, 0, High );


StaticVarSet( "buy", buy );
StaticVarSet( "sell", sell );
StaticVarSet( "long_bar", long_bar );
StaticVarSet( "prev_barcount", BarCount );

SetChartOptions( 0, chartShowArrows | chartShowDates );
_N( Title = StrFormat( "{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}}", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ) );
Plot( C, "Close", ParamColor( "Color", colorDefault ), styleNoTitle | ParamStyle( "Style" ) | GetPriceStyle() );

@bobptz I gave a glance at your code.

To me, it seems equivalent to this loop that set all the buys/sells bars to allow a backtest and to plot the signals.

SetBarsRequired( sbrAll, sbrAll );

// Try different combinations to simulate something more realistic...
SetTradeDelays( 0, 0, 0, 0 );
BuyPrice = High; // pessimistic buy
SellPrice = Low; // pessimistic sell
// SetTradeDelays( 1, 1, 0, 0);
// BuyPrice = SellPrice = Open; // Avg ??

Buy = Sell = 0;
long_bar = 0;

for( i = 0; i < BarCount; i++ )
{
    if( long_bar == 0 ) // no open position
    {
        // Now set the signal of the last bar, based on our rules (every BULL bar is a BUY signal)
        if( O[i] < C[i] )
        {
            Buy[i] = True;
            long_bar = i;
        }
    }
    else  // we have open position
    {
        if( long_bar <= ( i - 4 ) ) // hold for 4 bars - actually 5...
        {
            sell[i] = True;
            long_bar = 0;
        }
    }
}

/* Plot Buy/Sell Signal Arrows */
shape = Buy * shapeUpArrow;
PlotShapes( shape, colorBrightGreen, 0, Low );
shape = Sell * shapeDownArrow;
PlotShapes( shape, colorOrange, 0, High );

SetChartOptions( 0, chartShowArrows | chartShowDates );
_N( Title = "LOOP VERSION: " + StrFormat( "{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}}", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ) );
Plot( C, "Close", ParamColor( "Color", colorDefault ), styleNoTitle | ParamStyle( "Style" ) | GetPriceStyle() );

Above pane done with the Bar Replay tool running your code - Lower pane with the loop code.

immagine

I started the replay at 31-Aug-2018 - A different date may give another result/plot.
I suggest to define some extra rules to restart the counting at specific times (for instance at each open intraday, start of month, etc).

1 Like

In the above code, I used a loop since in my opinion, it is more "intuitively similar" to the bar replay (or to a live update) action.

With such simple rules, you can easily get the same result also with array processing

SetBarsRequired( sbrAll, sbrAll );

// Try different combinations to simulate some more realistic...
SetTradeDelays( 0, 0, 0, 0 );
BuyPrice = Close; 
SellPrice = Close; 

Buy = O < C;
Sell = 0;
ApplyStop( stopTypeNBar, stopModeBars, 4 ); 
Equity(1); // see https://www.amibroker.com/guide/afl/equity.html

// Rest of plotting code
// to change: shape = (Sell > 0) * shapeDownArrow;
4 Likes

Thank you very much @beppe for taking the time to look at it.

I am aware of how to make a loop to simulate the bar replay and enable the backtesting. However the actual trading system is much more complicated and I would prefer to leave the logic as is. ie the code should run in bar replay and live mode.

The question is how to use the different techniques of Backtesting, the advanced techniques, CBT, etc to backtest it.

I suspect that it cannot be done.

The only thing that came to my mind is this:
Run the code in Bar Replay, save the signals in a text file, then run another very small script that just reads the signals text file and feeds the backtester.

I was able to make it work. But I could not do any optimizations, so I was looking for a more elegant solution.

Beppe’s second post is the trading system. It’s a very simple system.

All that code you posted is to hide buy singles unless it’s the last visible bar.

The only trade logic in that code is “ buy = O<C”.

Be aware as well as he set buyprice to the close. Very easy to accidentally try and trade on the open but that’s forward looking as you don’t know if the open is less then the close until the close.

1 Like

Please do not try to make a simple formula trading system out of the very dummy example I gave.

The point is to find a way to backtest a trading system originally designed for live trading.

Please confirm to me that it cannot be done with simple or advanced backtesting techniques, so I can move forward with the solution I have already mentioned.

Maybe @Tomasz can make a comment on this, please?

Be aware as well as he set buyprice to the close. Very easy to accidentally try and trade on the open but that’s forward looking as you don’t know if the open is less then the close until the close.

@Metamega, you are right. That is exactly why I added the comments about trade delays/prices. Since the sample code was intended to replicate the OP idea to operate in RT, I imagined that doing it "live" it may be possible to attempt to trade at the close price (activating some sort of bar completion countdown), but the actual traded price may be different.
I will probably test the code using the open price (or if in a very short TF the Avg price) of the next bar (and /or consider some slippage too).

deleted my post, the case is pertaining to a Backtest over live trading.

In short: everything can be done.

You have created a "problem" and you are now trying to "solve" it in convoluted way. The thing is that you should write the original formula so it generates signals properly (array wise), not for "last bar only".
You are probably coming from Tradestation that forces this kind of thinking but really it is not necessary (see my other post: MarketPosition and alternate exit condition)

Again: everything can be done. You could wrap everything inside a for() loop where bi changes from 0 to BarCount-1 instead of using fixed

bi = BarCount - 1; // taken from your code

And it would work. That would emulate implicit Tradestation-like loop. But that is really not the way to go. It is waste of resources. It should be done properly, array-wise. Hire somebody who knows AFL and explain in plain English the system you are trying to code and programmer would write it for you correctly.

Thank you for the feedback TZ.

I have not used Tradestation, but I do have difficulty using array-only processing for my huge 3000 script, that tries to emulate Price Action logic.

I will ask around the forum for a couple of ideas, and if I find that all these can be done with array processing, I will seek the help of a pro Amibroker programmer.

Hi

Here is the Price Action logic for a simple trading. I would like to know if it would be easy to program this with efficient array-only code.

I could code this, but with extensive use of loops and matrixes.

  1. Find all Swing highs and swing lows, in the last 300 bars.
  2. A swing high is a bar that is higher than the 3 previous bars and the 3 following bars.
  3. Group all swing high/lows that are less than 5 pips away, ie do not allow lines with less than 5 pips distance. Take the mean value of each group.
  4. These "mean" value lines form the Support/Resistance structure.
  5. From those lines, remove (invalidate) the ones that were breached/crossed with very litle force, but do allow lines that were cleanly crossed with great strength and long candles.
  6. If price comes within 4 pips to such a line from bellow, with decelerating momentum, SELL.
  7. If price comes within 4 pips to such a line from above, with decelerating momentum, BUY.

By "decelerating momentum" I mean what is easy to spot visually on a chart, price is rising fast and then slowly flatens, as it approaches the resistance.

If it is possible, then any help/pointers would be appreciated.

I think there's a high potential to achieve that using array_based solution , however some points in your post is not clear enough , like in point 5 , you talked about strength and weakness cross but without any guidelines to tell the criteria of that strength and weakness, if you post your Loop-Matrix version , i may be able to convert the whole code to work with arrays.

Hi @bobptz,

Whether the system is easy to program or not, is perhaps a separate question to whether or not array-processing or loops are needed, maybe both are. It depends.

By-the-way, the description is a great starting point for defining what you want, and is generally how I begin - start at a high-level, then break it down into smaller pieces, before building it up.

There are a few things in your description that are a bit fuzzy. Can you define what you mean by the following:

  • step 1 - potentially 100 swing hi/lo values over the past 300 bars need to be stored for processing, for every bar in the array, at 1000 quotes, that's 3000 values. Is this what you had in mind?
  • step 2 - so, it's not possible to determine whether a swing hi/lo has actually occurred until 3 bars after it has occurred - I presume this is acceptable, and no forward-looking.
  • step 3 is not clear - can you rephrase it. What if they're all < 5 pips from one another?
  • step 4 appears to be a comment, rather than a component of the system. Clarify how this is used in the system, or remove it, to avoid ambiguity.
  • step 5 - Even though you've strung the following terms together in a single sentence, they're all ambiguous, until someone makes a decision about they should be defined: "breached/crossed", "with very little force", "cleanly crossed", "great strength" and "long candles". What happens to the mediocre ones? How do you define them?
  • steps 6 & 7 - "decelerating" and "momentum" are two separate things, and need to be defined more clearly than simply saying "easy to spot visually on a chart".

"flattens", "approaches", "resistance"

If you can break a problem down into bite-sized components, in the same that you've defined the steps above, and keep refining until there's no room left to "wiggle", then it becomes easier to combine them into something meaningful, and have confidence that each it doing what you think it should.

The definition of each component will then suggest/ influence how it can/ should/ needs to be programmed.

@Sebastian

  1. Find all Swing highs and swing lows, in the last 300 bars.

  2. A swing high is a bar that is higher than the 3 previous bars and the 3 following bars.

  3. Do not allow lines with less than 5 pips distance. If there are two lines less than 5 pips appart, then those two lines must belong to the same group and we need to take the mean of the entire group again.

  4. If a Support line is crossed, it is either invalidated or the line converts into Resistance.

We allow a breach up to 5 pips. This will just incorporate the new lower point into the Support.

If the breach is between 5 pips and 10 pips, and then price returns above support, the line is invalidated.

If the breach is larger than 10 pips and we reached those 10 pips in 3 bars or less, the line is still valid, we call this REVERSAL. Else the line is invlalidated.

If we have a new reversal close to the previous one, then the line is invalidated. By "Close to the previous one" I mean the bars that mark the reversal should be less than 15 bars appart.

  1. If price comes within 4 pips to such a line from bellow, while decelerating, SELL.

  2. If price comes within 4 pips to such a line from above, while decelerating, BUY.

  3. These specs assume EUR/USD symbol and the 1min chart, because they have those certain pips values hard-coded.

+++++++++++++++++++++++++++

@phase21

step 1 - I expect 20-40 swings in 300 bars. Could be more ofcourse. I do not follow for the rest of the sentense.

step 2 - Yes, a swing is valid 3 bars later only.

step 3 - Just rewrote it.

step 4 - Removed it.

step 5 - Removed it.

steps 6 & 7 - Removed the "momentum" word. About the "decelerating": Price falls rapidly with large candles that have little lower tails. As price comes closer to support, candles become smaller, candles start having lower tails etc. It is very easy to spot with the eye. I would really like to see an array implementation that detects this pattern. I think I did put it in plain English. A programmer with experience in such applications can now think how to code it.

If I was to implement this, I might go this way: I would check the linear regression lines of the LOWS. I am on the last bar (barcount-1), this is the rightmost bar of my regression line segment. The leftmost point is 3 bars to the right. I record the Slope. Then I start going 1 bar to the left at a time and record the new slopes. The more I go to the left, the more steep the slopes should become. This means I have a deceleration as I approach the support.

I am wondering if the above (linear regression logic) can be implemented with arrays.

CORRECTION:

If I was to implement this, I might go this way: I would check the linear regression lines of the LOWS. I am on the last bar (barcount-1), this is the rightmost bar of my regression line segment. The leftmost point is 3 bars to the left. I record the Slope. Then I start going 1 bar to the left at a time and record the new slopes of the segments:
slope1: barcount-4 ... barcount-1
slope2: barcount-5 ... barcount-1
slope3: barcount-6 ... barcount-1
slope4: barcount-7 ... barcount-1
slope5: barcount-8 ... barcount-1
slope6: barcount-9 ... barcount-1
slope7: barcount-10 ... barcount-1
slope8: barcount-11 ... barcount-1
slope9: barcount-12 ... barcount-1

The leftmost bar cannot go further left that a HighSwing. The more I go to the left, the more steep the slopes should become. ie slope9 steeper than slope8, which is steeper than slope7 etc. where slope1 is closer to flat. This means I have a deceleration as I approach the support.

Of course this is the ideal scenario and I need to take into account some anomalies that I can tolerate or smooth out. But this will become more obvious after experimenting with real examples. If the pattern is not very clear, I do not take the trade.

I find this algo inefficient and I would prefer an array-style implementation. I never managed to do it with arrays though.

Hi @bobptz,

Excellent, more detail.

The system description you've provided contain phrases generally used by discretionary traders, which is great for conveying a sense of what you want, but isn't detaled enough for programming. Unfortunately, artificial intelligence isn't advanced enough (or available in AB, yet) for our purposes.

Step 1: if I understand you correctly, you think there are 20+ swing points over the prior 300 bars when looking at the very last bar on the chart, at the right-hand edge. So, there are 20+ numbers that need to be assessed for that bar.

Programmatically, the logic needs to be applied to each and every bar that comes into AmiBroker - does the new data influence what went before?

So, logically, we need to apply Step 1 to each and every bar, ie. every bar needs to have 20+ values, therefore, if a symbol has 1000 bars * 20 values per bar = 20,000 values.

I think I've misunderstood your intention here.

Step 2: so there are a total of 7 bars to determine a swing, and the middle one must be the lowest bar for a swing low. What about an "outside bar" in the middle, with 3 bars before and after it that are not higher than it?

Step 3: you've used the word "lines", but not defined what this means. Do you mean the price/values of the swing points from Step 1 above?

What do you mean by "pips" (a term used by foreign exchange traders) - AB has reserved words called "PointValue" and "TickSize", which can be seen/set in the "Information" tab for each symbol. Here're extracts from the AB User Manual:

ticksize defines tick size used to align prices generated by built-instops (seeexplanations below) (note: it does not affect entry/exit prices specifiedby buyprice/sellprice/shortprice/coverprice)

pointvalue allows to read and modify future contract point value (see backtesting futures)
CAVEAT: this AFL variable is by default set to 1 (one) regardless of contents of Information window UNLESS you turn ON futures mode (SetOption("FuturesMode",True ))

What do you mean by "Take mean value of each group"? What group? How do you define "group"?

(New) Step 4: where did "Support" and "Resistance" come from - how do these relate to the earlier steps? How are they defined?

What action (if any) needs to be taken when "the line is invalidated"?

Just confirming, that a "new reversal close to the previous one", means that there must be >= 15 bars between any two "reversals", otherwise the 2nd one is "invalid"?

What happens when there are several of them "near" one another, how does that influence the 15-bar rule?

Step 6&7: In regard to "decelerating", how do you define "falls rapidly" in "Price falls rapidly with large candles that have little lower tails"? Define what you mean by a "large" candle, and "little" lower tails.

In regard to "I would really like to see an array implementation that detects this pattern" - yep, can be done, have done it, but it takes time to understand and work through the details!

In regard to:

I think I did put it in plain English. A programmer with experience in such applications can now think how to code it.

Yes, you've used the English language correctly, however, the terms are vague - the antithisis of programming, and could be interpreted as:

  • abdication of responsibility, ie. you'll get your people to talk to my people to sort out the details.
  • you want someone else to solve your problems for you - that costs.
  • you don't really intend to trade the system yourself.

None of this stuff is easy, and candlestick interpretation in-particularly is more art than a science, and if you read Steve Nison's books, they tend to be couched in vague terms that aren't easy to define programmatically - you need to develop a "sense" of what's going-on in the chart to be able to interpret their meaning.

For example, Nison says something along the lines of: "a doji is where the Open and Close are the same", and elsewhere "there are no rigid rules about how far apart they should be, it's subjective". Great! That's no help whatsoever! Un/fortunately, computers are precise -> so how close is close enough?

Would we regard the following as "dojis" (Agilent Tech, Yahoo data)?
image

I'll leave the linear regression suggestions alone for the time being. There's still more than enough to chew-on before getting into potential solutions.

As you've responded to @Sebastian and myself with separate descriptions, you might want to consider posting a single description, to avoid duplication, getting things out of synch. It would be better if everyone refers to the same one - just keep improving one of them, as things become clearer.

Thank you @phase21 for all the attention and time on this issue. Detailed information on everything you asked follows. However I am afraid we are missing the purpose of the initial post.

I am not looking for someone to code a complete trading system for me. I love to program and this would take away the fun from it. This is the reason I did not want to give precise specs. I was intentionally vague. I wanted to see the thoughts of other Amibroker programmers when faced with similar problems.

You have asked and I am giving those detailed specs to you. But really, what is the use of all these? Are you planning to code this for me? Please do NOT. All of these are already coded by me.

I came here because I want to LEARN. I am asking for help because I am afraid I am using the wrong programming style, regarding Amibroker:
I focus on the LAST bar and I try to analyze the past 150-300 bars, to see what support/resistance I have, what is the momentum etc, so I can generate buy/sell signals for this (last) bar.
I could not use purelly array-style code. I am using many loops and IF (not IIF) statements.

I thought that the type of problems I am trying to solve (Price Action trading) justifies this, but maybe I am wrong. So I wanted to ask the experienced programmers in this forum.

Great! It will help me very much if you can share more information on this.

Here are the answers you requested:

Step1:

Yes, I believe this is how array processing works.

Step 2:

Then logically this bar is both a swing high and swing low. I would make the algorith first check for HIGH and mark it as SwingHigh only.

Step 3:

Yes.

"If EUR/USD moves from 1.1050 to 1.1051, that .0001 USD rise in value is ONE PIP."

A line is the price level of a swing. In step1 the algo marks all swing prices. It then creates groups by combining all lines (swing price levels) in the following way:
a) Find the two lines that have the least distance (price difference) between them, but no more than 5 pips.
b) Remove those lines from our list and replace them with a line that has price value the mean price value of the previous two lines.
c) Repeat until there are no lines with distance less than 5 pips.

Support and Resistance lines are "lines" (from step 3). Support is a line that is bellow current price. Resistance is a line that is above current price.

It is removed from the list of lines.

No, it means that there must be >= 15 bars between any two "reversals", otherwise the line is invalidated.

The line is invalidated.

Step 6&7:

If I had to go into greater detail:
The difference between the Close of current bar minus the Close of the previous bar is larger by 20% than the Mean Average of the previous 10 bars. Just an example. I would make 20% and period=10 parameters to easily adjust it later.

"large" candle is (for example) larger by 20% that the mean average of last 10 candles.
"little" tail means the tail is less than 20% of H-L of the candle.
Again 20% and period=10 I would make them parameters for easy adjusting.

If there is anything else, I would be more than happy to clarify.

@bobptz,

I have a few other things to attend to, so will provide a more detailed response later.

In the meantime, it seems that you aren't quite sure how array processing works. It took me a while and experimentation to get my head around it, but once I "got it", I then started to see and think differently. There are times when it's appropriate, and others not.

I presume you've read the AB User Manual on "Understanding how AFL works", and looked at the topics on this forum for same.

Have a look through this code - perhaps as a starting point for discussion on how to enhance your existing code:

// Takes 4.109 ms for 4800 bars on my PC, using "Code check & Profile"
function isDojiLoop()
{
	for (elemPtr = 0; elemPtr < BarCount; elemPtr ++)
		result[elemPtr] = Open[elemPtr] == Close[elemPtr] ;
		
	return result ;
}

// Takes 0.080 ms on my PC
function isDojiArray()
{
	return Open == Close ;
}


tmpIsDojiLoop = isDojiLoop() ;

tmpIsDojiArray = isDojiArray() ;

SetChartOptions(0,chartShowArrows|chartShowDates);
_N(Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}}", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ));
Plot(Close, "Close", ParamColor("Color", colorDefault ), styleNoTitle | ParamStyle("Style") | GetPriceStyle() ); 

// This is mainly for debugging/ verification that they're both producing the same result.
Plot(tmpIsDojiLoop, "tmpIsDojiLoop", ParamColor("tmpIsDojiLoop Color", colorcycle), ParamStyle("tmpIsDojiLoop style", styleHistogram + styleOwnScale + styleThick)) ;  
Plot(tmpIsDojiArray, "tmpIsDojiArray", ParamColor("tmpIsDojiArray Color", colorcycle), ParamStyle("tmpIsDojiArray style", styleHistogram + styleOwnScale + styleThick)) ; 

Thank you for the examples. I am aware of these.

Also, points 1 and 2 (how to mark the swings) I could make myself with 6-7 lines of array style code.

For example the following code would mark swing highs, with 2 lower bars to the left and right.

// marks the SwingHigh points of this TF.
function markSwingHigh( P )
{
    local
    RightmostPossibleTurnBarIndex,   // No turn can exist to the right of this BarIndex
    RightL,    // The bars to the right are Lower, which makes the bar a HIGH candidate
    LeftL;     // The bars to the left are Lower, which makes the bar a High candidate

    LastCompleteBarindex = Barcount - 2;
    
// A Turn needs 2 bars to the right
    RightmostPossibleTurnBarindex =  LastCompleteBarindex - 2;

    RightL = IIf( TFBarIndex <= RightmostPossibleTurnBarindex,
                  ( P > Ref( P, 1 ) ) AND( P > Ref( P, 2 ) ), False );
    LeftL  = ( Ref( P, -2 ) < P ) AND( Ref( P, -1 ) < P );
  
 return (LeftL AND RightL); 
}

It is the rest of the points that I find difficult to code fast with arrays.