PositionScore / Ranking for trades taken “next day at limit”

I am looking to utilize PositionScore / Rank. I found the following article to be quite helpful, but am not sure my interpretation of the code is correct. And if so, then I need a suggestion to achieve my objective, which I still seem to be failing at.

http://www.amibroker.com/kb/2014/11/26/handling-limit-orders-in-the-backtester/

For the example, I created a simple system that looks to buy the morning after a 5-day closing low that occurs above the 200-day moving average. The trigger-day’s closing price is used as a limit price for the potential buy. The system will then sell a close at a new 5-day high. (I ran it across the S&P 100 for testing.) It takes a 10% position in each trade and up to 10 positions.

Buy = Sell = Short = Cover = 0;

SetOption("MaxOpenPositions", 10);
SetPositionSize(10,spsPercentOfEquity);


isAbove = C > MA(C, 200);
isLow = C == LLV(C, 5);


Buysignal = isAbove AND isLow ;

Buy = Ref(Buysignal, -1);  //  buy on next bar
BuyLimitPrice = Ref(C, -1);  // limit price

Buy = Buy AND L <= BuyLimitPrice; // check if limit was hit
BuyPrice = Min(Open, BuyLimitPrice);


Sell = C == HHV(C, 5); //Sell when security closes at a 5-day high

PositionScore = Ref(100-ROC(Close,5),-1); //Most oversold over the last 5 days are ranked highest

I believe what this is doing is forward looking at all the securities that 1) closed above the 200ma, 2) closed at a 5-day low , and 3) did not leave an unfilled up gap the next. It then ranks them and enters the Top 10 most oversold based on ROC on the day they closed at a 5-day low.

The problem as I see it is that my code knows in advance which securities are not going leave unfilled gaps up. So if I have room in the portfolio for 1 stock, and AAPL and MSFT both close at 5-day lows above the 200ma. And we then assume that AAPL is the most oversold, but it leaves an unfilled gap up the next day. MSFT does NOT leave an unfilled gap up. Then MSFT would get purchased in this backtest. This would fill out the last spot in the portfolio. But in reality, I need the backtest to show NOTHING got purchased because the AAPL entry was missed and I only had room in this example for 1 trade. (Because that is what would happen in my own trading if I put in a trade or list of trades to hopefully get filled the next day.)

I also tried this code (from lower down in the same article).

Buy = Sell = Short = Cover = 0;

// we run the code on WatchList 15 (S&P 100)
List = CategoryGetSymbols( categoryWatchlist, 15 );
SetOption("MaxOpenPositions", 10);
SetPositionSize(10,spsPercentOfEquity);
 
if ( Status("stocknum") == 0 ) // Generate ranking when we are on the very first symbol
{
     StaticVarRemove( "values*" );

     for ( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++    )
     {
         SetForeign ( symbol );
        
         // value used for scoring
         values = Ref(100-ROC(Close,5),-1);
         RestorePriceArrays();
         StaticVarSet (  "values"  +  symbol, values );
         _TRACE( symbol );
     }

     StaticVarGenerateRanks( "rank", "values", 0, 1224 );
}

symbol = Name();
values = StaticVarGet ( "values" +  symbol );
rank = StaticVarGet ( "rankvalues" +  symbol );

PositionScore = values;

isAbove = C > MA(C, 200);
isLow = C == LLV(C, 5);

Buysignal = isAbove AND isLow ;


// buy on the next bar
Buy = Ref( BuySignal, -1);
BuyLimitPrice = Ref( Close, -1);

// now we check if limit was hit for the symbols ranked as top 10
Buy = Buy AND L < BuyLimitPrice AND rank <= 10;
BuyPrice = Min( Open, BuyLimitPrice );


Sell = C == HHV(C, 5); //Sell when security closes at a 5-day high

This also does not seem to be accomplishing what I hoped (though it does seem useful in another way). My interpretation is this:
All stocks are pre-ranked based on their 5-period ROC. If any of the top 10 close at a 5-day low and above the 200ma, then they will be bought the next day at a limit price equal to the trigger-day’s close. In this case, the possibility of triggering a substantial number of trades is reduced. It is not just ranking the ones that qualify and attempting next-day limit orders on them. It is only looking to trade the top 10 of the 100 ranked. (I ran this on the S&P 100). So in this case, only the 10 most oversold stocks could qualify. But if 7 of those stocks were below their 200ma, then there could only possibly be 3 that might close at a 5-day low and > 200ma.

I do want to make sure my interpretation here is correct, because this is a useful concept. For instance, I could envision using a longer-term measure here that would only look to trade in the top half or bottom half of stocks based on 200-day ROC, and then rank whichever of those that qualify for trades that night by their 5-day ROC. But I am getting ahead of myself there.

The bottom line questions are:

  1. Are my interpretations of the above code correct?
  2. In the backtester, how would I look to buy the most oversold the next day at a limit price without the look-ahead bias of knowing whether the limit price was going to be available when doing the ranking?

Thanks!

6 Likes

I believe you are interpreting the code correctly. What you are doing with the Rank value in this example is basically equivalent to constraining the number of limit orders you place for the next day. The problem is, you're going to place up to 10 orders for tomorrow, even if you still have 6 open trades today, so you have the potential to end up with 16 open trades (though you will likely run out of trading capital first). Even if you do run out of capital, you may enter trades for symbols that were ranked 4,5,7 and 10, when in fact you probably should have only taken the trade for Rank #4 and ignored the rest.

In my opinion, the best way to resolve this issue is to write your own Custom Back Test (CBT). That way you don't need to do the ranking, and you can just send all "orders" (Setups) to the CBT as if they were entries. Those entry signals will be ranked by PositionScore, and you can directly enforce only considering the first N signals, where N is the number of additional positions you are willing to take on that day.

3 Likes

@QEdges I agree with @mradtke that you appear to be correct with your interpretation. Also the CBT may provide more flexibility in the code.

But at least in terms of

You could make a slight change to your ranking method, and only assign acceptable scores to stocks trading over their 200 day m.a.

values = IIf(C>MA(C, 200), Ref(100-ROC(Close,5),-1), 0);

The difference in a typical day's ranking might look like this,

Your original ranking,

image

Assigning a value of zero to stocks below the threshold m.a. gives this,

image

6 Likes

Right you are, @portfoliobuilder. I skipped over that subtlety and went right for the CBT jugular. :slight_smile:

2 Likes

Thank you @mradtke and @portfoliobuilder !
I am not yet up to speed with utilizing the CBT beyond the simple examples in the documentation, so I decided to give @portfoliobuilder ‘s idea a try. Simple and brilliant! But I will note that I had to adjust the code slightly.

The suggestion to add the 200ma filter as an “iif” condition in the code does not quite get you there. In this simple example, it should be fine, but as more conditions are added, more conditions need to be accounted for. So I used the iif statement to recreate the Buy conditions. I needed to put similar (but different) variables in the loop. Also needed to look back at yesterday’s conditions. (This is because the rank is being done on the day of the buy signal, so to exclude those that do not qualify we really need to check conditions as of yesterday’s close, as was done with the actual buy.) The beauty here is that you can just mimic the buy criteria within the loop and then it will only be ranking those stocks that qualify for a buy as of yesterday’s close – just as I was hoping!

Here is my updated code:

Buy = Sell = Short = Cover = 0;

// we run the code on WatchList 0
List = CategoryGetSymbols( categoryWatchlist, 15 );
SetOption("MaxOpenPositions", 10);
SetPositionSize(10,spsPercentOfEquity);
SetOption("AllowSameBarExit", True ); 


 
if ( Status("stocknum") == 0 ) // Generate ranking when we are on the very first symbol
{
     StaticVarRemove( "values*" );

     for ( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++    )
     {
         SetForeign ( symbol );
        
        isAboveR = C > MA(C, 200);  
		isLowR = C == LLV(C, 5);
		
         // value used for scoring
         values = IIf(Ref(isAboveR,-1) AND Ref(isLowR,-1), Ref(100-ROC(Close,5),-1), 0);  ///////slight change to @portfoliobuilder's code to include all required conditions and lok at day before
         RestorePriceArrays();
         StaticVarSet (  "values"  +  symbol, values );
         _TRACE( symbol );
     }

     StaticVarGenerateRanks( "rank", "values", 0, 1224 );
}

symbol = Name();
values = StaticVarGet ( "values" +  symbol );
rank = StaticVarGet ( "rankvalues" +  symbol );

PositionScore = values;

isAbove = C > MA(C, 200);  
isLow = C == LLV(C, 5);

Buysignal = isAbove AND isLow ;


// buy on the next bar
Buy = Ref( BuySignal, -1);
BuyLimitPrice = Ref( Close, -1);


// now we check if limit was hit for the symbols ranked as top 10
Buy = Buy AND L < BuyLimitPrice AND rank <= 10;
BuyPrice = Min( Open, BuyLimitPrice );

/*
////////////////////Sell next day's open
SellSignal = C == HHV(C, 5);
Sell = Ref(SellSignal, -1) OR exitLastBar; //Sell when security closes at a 5-day high
SellPrice = Open;
////////////////////End Sell next day's open
*/


Sell = C == HHV(C, 5); //Sell when security closes at a 5-day high

//////////////////// removing buys you don't want
intrade = False;
for( i = 0; i < BarCount; i++ )
{
	if( NOT intrade )
	{
		if( Buy[ i ] ) 
		{
			intrade = True;
		
			// same bar sell
			if( Sell[ i ] ) intrade = False;
		}
	}
	else
	{
		if( Sell[ i ] ) 
		{
			intrade = False;
			Buy[ i ] = False; // remove buy if exited this bar
		}
	}
}  
//////////////////// end removing buys you don't want

I will also note that the ability to buy the day after a 5-day low, but exiting at the close did create issues with double entries. To see what I mean, see the chart below, which is of UNP.

UNP

There were 2 fixes for this that I found:

  1. Exit trades at the next day’s open, rather than at the close. Code to do this is commented out. People may not find this acceptable if they really want to exit at the close.
  2. Utilize the “removing buys” code that @Tomasz provided in this thread: https://forum.amibroker.com/t/using-or-in-a-sell-condition-allows-mutliple-entries-on-same-symbol/6888/11 That is now included and seems to work well.

One thing I have not done yet is include the “removing buys” code in the ranking loop. Without doing this I believe it could rank UNP high in the ranking loop on the noted day, even though it won’t buy it later. The net effect of this would be to possibly miss a few trades if there were several that would have triggered that day and one that could have entered got bumped out of the Top X ranking by UNP. Not a big issue, and I can probably get the “removing buys” loop in the “ranking loop”, I just have not managed it yet but thought I would update my progress.

Thanks all!

1 Like

@QEdges, I believe that you still have a problem when using this solution. Consider the following scenario:

  • You want to allow a max of 10 open positions, and you are not placing trades using margin
  • At Monday's close, you have 8 open positions
  • Monday night you do your ranking, and there are fives stocks that meet the Setup criteria, and therefore receive ranks 1-5
  • Your Buy logic is:
    Buy = Buy AND L < BuyLimitPrice AND rank <= 10;
  • Therefore, on Tuesday you will enter a trade for the stock with a rank of 4. However, in live trading you could only do this by using margin or by placing the orders in real time when the stock price fell below the limit.

Perhaps this is of small concern when your limit price is yesterday's close. But as your limit price gets more restrictive, this approach will skew your results more and more. If you're willing to place trades on margin (i.e. beyond your available capital), then no problem.

3 Likes

Hi @mradtke

In actual trading, I would use margin, and I would check the trades at night before submitting them for the next day. In other words, I should be able to easily know whether I will have room to execute if I really did want to max out at 10 trades. But...I want the backtest to mimic real life (which is why I am going through all this). So the situation you describe would be troubling.

But looking at the detailed backtest I do not see it playing out according to your concern (though I may be missing something). See screenshot below.

5-low%20buys

My thought was that setting the "MaxOpenPositions" at the top the code like I did (or at the backtest settings) would take care of this. And while I am not 100%, to my eye, it did seem to. Please correct me if you believe differently.

Thanks.

The issue is that you are generating Buy signals by peeking into the future, i.e. only setting Buy = True when the Low is less than the Limit price. In essence you are saying "On Monday night I will only place limit orders for stocks that are going to decline in price on Tuesday".

With your current code, 17-July-18 you created 4 Buy signals for symbols that had a rank less than 10, and yes, AmiBroker correctly entered only 2 trades before it hit your max of 10 open positions. But how did those 4 Buy signals rank among the 10? They could have been 7,8,9, and 10, which means you would have needed to place orders for at least 8 symbols on the night of 16-July. And since you didn't know that 4 stocks would meet the limit, you really would have had to place orders for all 10, because what if only ONE stock hit the limit, and it was ranked 10th?

As I said previously, if you're willing to use margin to place trades, then this may all be fine. But if not, then you're cheating by looking into the future.

1 Like

Aaargh. Yes. I see what you are saying, and that is what this whole exercise was trying to avoid. Thank you for hammering that in @mradtke , as I was not getting it at first! I believe this could be fixed if I could change the buy statement to something like:

Buy = Buy AND L < BuyLimitPrice AND rank <= (10 - currentOpenPositions);

Though Current Open Positions is listed in the backtest detail, I do not understand quite how to grab that number.

This post from @Tomasz appears to address it using CBT, as you suggest needs to be done.

https://forum.amibroker.com/t/marketposition-and-alternate-exit-condition/4668/4?u=tomasz

But I am afraid I am not instituting properly. Below is my current attempt.

Buy = Sell = Short = Cover = 0;

// we run the code on WatchList 0
List = CategoryGetSymbols( categoryWatchlist, 15 );
SetOption("MaxOpenPositions", 10);
SetPositionSize(10,spsPercentOfEquity);
SetOption("AllowSameBarExit", True ); 


///////////////////////////////////////@Tomasz code inserted here to try and find current open positions
SetCustomBacktestProc( "" );

if ( Status( "action" ) == actionPortfolio )
{
    bo = GetBacktesterObject();    //  Get backtester object
    bo.PreProcess();    //  Do pre-processing (always required)

    for ( i = 0; i < BarCount; i++ )  //  Loop through all bars
    {
        bo.ProcessTradeSignals( i );  //  Process trades at bar (always required)

        for ( openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() )
        {
            // openpos variable now holds Trade object - you can query it

        }
    }    //  End of for loop over bars

    bo.PostProcess();    //  Do post-processing (always required)
}
///////////////////////////////////////end of current open positions code

 
if ( Status("stocknum") == 0 ) // Generate ranking when we are on the very first symbol
{
     StaticVarRemove( "values*" );

     for ( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++    )
     {
         SetForeign ( symbol );
        
        isAboveR = C > MA(C, 200);  
		isLowR = C == LLV(C, 5);
		
         // value used for scoring
         values = IIf(Ref(isAboveR,-1) AND Ref(isLowR,-1), Ref(100-ROC(Close,5),-1), 0);  ///////slight change to @portfoliobuilder's code to include all required conditions and lok at day before
         RestorePriceArrays();
         StaticVarSet (  "values"  +  symbol, values );
         _TRACE( symbol );
     }

     StaticVarGenerateRanks( "rank", "values", 0, 1224 );
}

symbol = Name();
values = StaticVarGet ( "values" +  symbol );
rank = StaticVarGet ( "rankvalues" +  symbol );

PositionScore = values;

isAbove = C > MA(C, 200);  
isLow = C == LLV(C, 5);

Buysignal = isAbove AND isLow ;


// buy on the next bar
Buy = Ref( BuySignal, -1);
BuyLimitPrice = Ref( Close, -1);


// now we check if limit was hit for the symbols ranked 
Buy = Buy AND L < BuyLimitPrice AND rank <= (10 - openpos); //my attempt at pulling openpos from the CBT code near the top that looks for the open positions, but this line will not verify,  States that openpos was not initialized
BuyPrice = Min( Open, BuyLimitPrice );

/*
////////////////////Sell next day's open
SellSignal = C == HHV(C, 5);
Sell = Ref(SellSignal, -1) OR exitLastBar; //Sell when security closes at a 5-day high
SellPrice = Open;
////////////////////End Sell next day's open
*/


Sell = C == HHV(C, 5); //Sell when security closes at a 5-day high

//////////////////// removing buys you don't want
intrade = False;
for( i = 0; i < BarCount; i++ )
{
	if( NOT intrade )
	{
		if( Buy[ i ] ) 
		{
			intrade = True;
		
			// same bar sell
			if( Sell[ i ] ) intrade = False;
		}
	}
	else
	{
		if( Sell[ i ] ) 
		{
			intrade = False;
			Buy[ i ] = False; // remove buy if exited this bar
		}
	}
}  
//////////////////// end removing buys you don't want

If there is a simple way to throw a Current Open Position number in there that someone knows, then I would appreciate a suggestion. Otherwise, I will look to dig into CBT a bit more next week.

Thanks!

1 Like

@QEdges you have a good start there. For completeness, I would use all three of these lines when you're implementing a CBT:

SetOption("usecustombacktestproc", True);
SetCustomBacktestProc("");
SetBacktestMode(<your desired mode here>);

Also add some _TRACE() statements to verify that you're actually executing your CBT. Over time, you will find that TRACE() is your best friend for verifying the operation of your AFL.

The next thing you need to do is add some logic before you call bo.ProcessTradeSignals. Specifically, you need to see how many open positions you have, which tells you how many orders you can place on the current bar. That, in turn, tells you how many entry signals to keep so that you can disable all the remaining ones. One way to do that is to simply set the position size to 0 for any signals that you want to ignore.

1 Like

@QEdges the CBT is really best left to advanced AmiBroker users.

With that caveat out of the way I can take a stab as a non-advanced user with some ideas for you to investigate. In your CBT you loop through the open positions but did not "do anything" in that loop. I think one method of obtaining the number of current open positions may be like this,

// Count number of open positions
OpenPos = 0;

for( trade = bo.getFirstOpenPos(); trade; trade = bo.getNextOpenPos() )
{
    OpenPos++;
}

How to use that information is I believe dependent on the rest of your code. Setting a StaticVariable is one possible way that I can think of to access this info later,

StaticVarSet(myStrategy + "OpenPos", OpenPos);

I think another method to access the number of positions is via the backtester object and the property of GetOpenPosQty

In the CBT something like this may work, as you loop bar-by-bar,

	PosQty = 0;

	for (i = 0; i < BarCount; i++) // Loop through all bars
	{
		PosQty = bo.GetOpenPosQty();
// and go on with your code from here

but please take all of the above with a large grain of salt as I am just in the process of learning how to use the CBT and far from an expert.

Good luck!

1 Like

Hi all

Just saw this thread and has helped me confirm my own back test using limit orders is a bit like having a crystal ball! did anyone ever manage to get a CBT working for this?

As is usually the case with AmiBroker, there are multiple ways to solve this problem. I prefer to do it by sending all Setups to the CBT as if they were entries, which allows me to count how many "orders" I'm placing. What have you tried, and what is not working for you?

Looking at the code again at the bottom of the web page, "Handling limit orders in the backtester", the Buy is looking at yesterday's BuySignal but is the rank being calculated using the close of the same day of the buy at a limit on the open? Should a line be added such as

rank= Ref( rank, -1);

or is it OK the way it is?
Web page I'm referring to:
https://www.amibroker.com/kb/2014/11/26/handling-limit-orders-in-the-backtester/

2 Likes

@Marcel: I believe that both rank (used in the Buy assignment) and values (used for PositionScore) should be referencing the previous bar's data, i.e. they should be determined at the same time as BuySignal. However, you probably should get confirmation from @Tomasz since it's his code. In the meantime, you could verify the values with an Exploration.

2 Likes

I ran a backtest and exploration on the code for limit orders with multiple positions. I ran the code against the default watchlist number 0 which for Norgate, corresponds to "Dow Jones Industrial Average Current & Past" This is a copy of the exploration for the most recent trade that shows up on February 14 2019:
image

I can see in the exploration that it is looking at the rank calculated for Feb 14 which is 3. The previous day's rank is 34 so it couldn't be looking at that to qualify the trade. The Feb 14 rank is calculated from the RSI which looks at the same day close value. As far as I understand, this would be a future leak. If anyone else is interested to look, I've copied the code below which is identical to the one found on this page, except that I've added an exploration on the end:
https://www.amibroker.com/kb/2014/11/26/handling-limit-orders-in-the-backtester/

// we run the code on WatchList 0
List = CategoryGetSymbols( categoryWatchlist, 0 );
SetOption( "MaxOpenPositions", 3 );

if( Status( "stocknum" ) == 0 ) // Generate ranking when we are on the very first symbol
{
    StaticVarRemove( "values*" );

    for( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++ )
    {
        SetForeign( symbol );

        // value used for scoring
        values = 100 - RSI();
        RestorePriceArrays();
        StaticVarSet( "values"  +  symbol, values );
        _TRACE( symbol );
    }

    StaticVarGenerateRanks( "rank", "values", 0, 1224 );
}

symbol = Name();
values = StaticVarGet( "values" +  symbol );
rank = StaticVarGet( "rankvalues" +  symbol );

PositionScore = values;

BuySignal = Cross( Close, MA( Close, 100 ) );

// buy on the next bar
Buy = Ref( BuySignal, -1 );
BuyLimitPrice = Ref( Close, -1 ) * 0.999;

// now we check if limit was hit for the symbols ranked as top 3
Buy = Buy AND L < BuyLimitPrice AND rank <= 3;
BuyPrice = Min( Open, BuyLimitPrice );

// sample exit rules - 5 - bar stop
Sell = 0;
ApplyStop( stopTypeNBar, stopModeBars, 5, 1 );

Filter = 1;
AddColumn( Ref( Close, -1 ) * 0.999, "EntryLimit", 1.2 );
AddColumn( Open, "Open", 1.2 );
AddColumn( Low, "Low", 1.2 );
AddColumn( Close, "Close", 1.2 );
AddColumn( RSI(), "RSI", 1.2 );
AddColumn( values, "values", 1.2 );
AddColumn( rank, "rank", 1.0 );
AddColumn( ref( RSI(), -1 ), "YdayRSI", 1.2 );
AddColumn( Ref( values, -1 ), "Ydayvalues", 1.2 );
AddColumn( Ref( rank, -1 ), "Ydayrank", 1.0 );
AddColumn( Ref( BuySignal, -1 ), "YdayBuySignal", 1.0 );
AddColumn( IIF( Buy, 'B', ' ' ), "Buy", formatChar, colorDefault, IIf( Buy, colorLime, colorDefault ) );
AddColumn( IIF( Sell, 'S', ' ' ), "Sell", formatChar, colorDefault, IIf( Sell, colorRed, colorDefault ) );
1 Like

I think that the story does not finish here @mradtke
I use your example:

  • You want to allow a max of 10 open positions, and you are not placing trades using margin
  • At Monday's close, you have 8 open positions
  • Monday night you do your ranking, and there are fives stocks that meet the Setup criteria, and therefore receive ranks 1-5

It's right, you can use margin and place all 5 limit orders on the market. However In this case you incur in the risk of buy all 5 stocks and in this case the strategy became a totally different strategy respect of what Amibroker is backtesting.
Alternately you can place the orders in real time when the stock price fell below the limit, but as a i said the story does not finish here.
In fact , in our example, imagine that monday night we find 3 stocks that meet the setup criteria: stock1 (rank1) stock2 (rank2) stock3 (rank3). Remeber that we already have 8 open positions.
You decide to follow live the market before place your orders.
At 10am none of the 3 stocks met their price limit.
At 11am stock3 fell below the price limit. What you have to do in this case? You decide to buy stock3.
At 12pm also stock2 fell below the price limit and you are forced to buy this stocks because it can go higher not giving you a second chance.
Now all your 10 slots are filled.
Everything is going to be ok but at 15pm also stock1 fell below its price limit.
You don't buy stock1 because all the 10 slots are filled.
The problem is that amibroker , at the end of that day, will buy stock1 and stock2 (leaving unfilled stock3) because they have a better rank.

All that said, is correct to make the following statement? :
The code's structure of @QEdges (the last one) is completly impossibile to execute in reality.

In other word there is no way to be allined with what Amibroker do.

It follows that Amibroker (in this case) is testing something that is not realistic because there is no way to be deterministic in the live execution.

We should rethink the code structure so that Amibroker can do a realistic backtest.

2 Likes

It's easy to do a realistic backtest with the CBT. If you only have two open slots on Tuesday, then you only consider the first two Setups, i.e. the Buy logic without a limit price check. The Setups may or may not breach the limit price and result in an entry.

@mradtke would you be so kind to give me an example to study? In the contest of our example how would you do a perfectly realistic backtest?
I would appreciate a lot.

I would be interested to know how to deal with the issue @Heisenberg postulated.

You said that we can deal with it using CBT.
Here is what I think:

  1. For EOD strategies like this which rely on limit orders, I should use 1min data or even better tick by tick data so the backtester simulates real world equity requirements and order sequence correctly.

  2. Using a CBT, I can send a Buy order without the limit price check but since there is no way to know which of the stock breached the limit price first, I cannot rely on it to decide which stock to take position in.

  3. In absence of 1min/tick data, I don't use percentage based position sizing currently. I start with a huge initial equity and use static absolute value based positions so that I have a better idea of strategy usability. But this method has its disadvantages like for one, I cannot see the compounded equity and have to rely on Win-rates to evaluate strategy.

As I see it, only 1st point is the most reliable way to simulate such conditions.
Your suggestions are greatly appreciated.