Nick Radge / Alvarez Mean Reversion Ideas - do you get the same results? I don't

Hi,
Some of my MR systems for stocks took a beating during the last weeks so probably time for some more tests - I thought.
Long story short, I wanted to play around with a limit order entry. I stumbled across this page (once again).

Nick Radge / Alvarez MR strategy w limit order entry

The strategy was programmed in a few minutes using Norgate Data. I can't get the thing anywhere close to what has been published. I wonder if I did something wrong and what it could be.

Did someone here play around and can shed some light on it?
...it works best with Norgate Data.

Dates simulated for comparison (same as webpage):
01/01/2014 - 06/30/2014

I didn't even include commissions or slippage.
The system just evaporated money...

image

for comparsion:
source:
source
image
image

#include_once "Formulas\Norgate Data\Norgate Data Functions.afl"
OnSecondLastBarOfDelistedSecurity = !IsNull(GetFnData("DelistingDate")) AND (BarIndex() == (LastValue(BarIndex()) -1) OR DateTime() >= GetFnData("DelistingDate") ) ;
OnLastTwoBarsOfDelistedSecurity = !IsNull(GetFnData("DelistingDate")) AND (BarIndex() >= (LastValue(BarIndex()) -1) OR DateTime() >= GetFnData("DelistingDate") );

index_string1 ="$RUA";
constituent = NorgateIndexConstituentTimeSeries(index_string1);

//Max. Pos. Held
qty=							10;//Optimize("qty",30,10,70,5);// 

//Limit-Entry
lower = 0.5;//Optimize ("limit",1,0.5,3,0.5);
limit = C<=Ref(C,-1)-lower*Ref(ATR(10),-1);

BuyPrice=						Ref(C,-1)-lower*Ref(ATR(10),-1); // simulate Limit Price
SellPrice=						O;// no slippage
PctSize =    					100/qty; //simple % alloc.

SetPositionSize(PctSize, spsPercentOfEquity);  

//Back-Tester Initial Settings
//SetBarsRequired(200);//
SetOption("NoDefaultColumns",True);//
SetOption("InitialEquity",100000);//
//SetOption("AllowSameBarExit",True);//
SetOption("ActivateStopsImmediately",false);//TRUE when trading on OPEN
SetOption("AllowPositionShrinking",False);//
SetOption("FuturesMode",True);//
SetOption("AccountMargin",100);//
SetOption("InterestRate",0);//
SetOption("maxopenpositions",qty);//
//EnableRotationalTrading()
//SetOption("WorstRankHeld",false);// rot trading only
SetOption("MinShares",1);//
SetOption("MinPosValue",0);//
SetOption("PriceBoundChecking", False);//
SetOption("CommissionMode",3);// 0 - use portfolio manager commission table 1 - percent of trade 2 - $ per trade 3 - $ per share/contract
SetOption("CommissionAmount",0.00);//
SetOption("ReverseSignalForcesExit", False);//
SetOption("UsePrevBarEquityForPosSizing", True);//
SetOption("PortfolioReportMode", 0);// 0: trade list 1: detailed log 2: summary 3: no output
SetOption("UseCustomBacktestProc",False);//
SetOption("EveryBarNullCheck",False);//
SetOption("HoldMinBars",0);//
SetOption("EarlyExitBars",0);//
SetOption("EarlyExitFee",0);//
SetOption("EarlyExitDays",0);//
SetOption("DisableRuinStop",True);//
//SetOption("GenerateReport",1)
SetOption("SeparateLongShortRank",False);//
//SetOption("MaxOpenLong",0);// 
//SetOption("MaxOpenShort",0);//
SetOption("RefreshWhenCompleted",False);//
SetOption("ExtraColumnsLocation",False);//
//SetOption("SettlementDelay",0);//
OptimizerSetEngine("cmae");  
SetTradeDelays(0,1,0,0);// 
SetBacktestMode( backtestRegular); 
//SetOption("portfolioreportmode",1);//

Short=0;
Cover=0;

//Historical Vola filter / PositionScore
HV = 100 * StDev(log(C/Ref(C,-1)),100) * sqrt(252);

///////////////////////////////////////////////////////////////
//Buy Condition:
turnover = MA(V,21)*C>10000000;//
oc = NorgateOriginalCloseTimeSeries()>1;
buy1= L<Ref(L,-1) AND Ref(L,-1)<Ref(L,-2) AND Ref(L,-2)<Ref(L,-3) AND C<MA(C,5) AND C>MA(C,100) AND turnover AND oc;//

/////////////////////////////////////////////////////////////////

Buy=				Ref(buy1,-1) AND limit  AND constituent  AND NOT OnSecondLastBarOfDelistedSecurity;// 
Sell=				C>Ref(C,-1) OR OnSecondLastBarOfDelistedSecurity;
Buy = 				ExRem(Buy,Sell); 
Sell = 				ExRem(Sell,Buy); 

positionScore=		HV; // rank by HV or mtRandom()
2 Likes
limit = C<=Ref(C,-1)-lower*Ref(ATR(10),-1);

//change to
limit = L<=Ref(C,-1)-lower*Ref(ATR(10),-1);

@Dionysos: that's also the wrong way to write a strategy which uses limit orders. You need to use the CBT to do it correctly. See this post: PositionScore / Ranking for trades taken “next day at limit”

3 Likes

Changing the "limit" line correctly should fix the results. But as mentioned in the blog post the PositionScore needs to be set to Random() because you do not know what order the trades would trigger.

1 Like

Thanks! that's a helpful pointer, I didn't notice the thread before.

Simulating the limit price was obviously a problem.
Let me also use mtRandom() as Positionscore. I'm still using no slippage for the exit, but deduct 0.01$ per share in commissions.

#include_once "Formulas\Norgate Data\Norgate Data Functions.afl"
OnSecondLastBarOfDelistedSecurity = !IsNull(GetFnData("DelistingDate")) AND (BarIndex() == (LastValue(BarIndex()) -1) OR DateTime() >= GetFnData("DelistingDate") ) ;
OnLastTwoBarsOfDelistedSecurity = !IsNull(GetFnData("DelistingDate")) AND (BarIndex() >= (LastValue(BarIndex()) -1) OR DateTime() >= GetFnData("DelistingDate") );

index_string1 ="$RUA";
constituent = NorgateIndexConstituentTimeSeries(index_string1);

//Max. Pos. Held
qty=							10;//Optimize("qty",30,10,70,5);// 

//Limit-Entry
lower = Optimize ("limit",0.5,0.5,2.6,0.2);
limit = L<=Ref(C,-1)-lower*Ref(ATR(10),-1);
slippage=1;
BuyPrice=						Ref(C,-1)-lower*Ref(ATR(10),-1); // simulate Limit Price
SellPrice=						O;//no slippage //with slippage O-Ref(ATR(10),-1)*slippage/100;
PctSize =    					100/qty; //simple % alloc.

SetPositionSize(PctSize, spsPercentOfEquity);  

//Back-Tester Initial Settings
//SetBarsRequired(200);//
SetOption("NoDefaultColumns",0);//
SetOption("InitialEquity",100000);//
SetOption("AllowSameBarExit",false);//
SetOption("ActivateStopsImmediately",false);//TRUE when trading on OPEN
SetOption("AllowPositionShrinking",False);//
SetOption("FuturesMode",True);//
SetOption("AccountMargin",100);//
SetOption("InterestRate",0);//
SetOption("maxopenpositions",qty);//
//EnableRotationalTrading()
//SetOption("WorstRankHeld",false);// rot trading only
SetOption("MinShares",1);//
SetOption("MinPosValue",0);//
SetOption("PriceBoundChecking", False);//
SetOption("CommissionMode",3);// 0 - use portfolio manager commission table 1 - percent of trade 2 - $ per trade 3 - $ per share/contract
SetOption("CommissionAmount",0.01);//
SetOption("ReverseSignalForcesExit", False);//
SetOption("UsePrevBarEquityForPosSizing", True);//
SetOption("PortfolioReportMode", 0);// 0: trade list 1: detailed log 2: summary 3: no output
SetOption("UseCustomBacktestProc",False);//
SetOption("EveryBarNullCheck",False);//
SetOption("HoldMinBars",0);//
SetOption("EarlyExitBars",0);//
SetOption("EarlyExitFee",0);//
SetOption("EarlyExitDays",0);//
SetOption("DisableRuinStop",True);//
//SetOption("GenerateReport",1)
SetOption("SeparateLongShortRank",False);//
//SetOption("MaxOpenLong",0);// 
//SetOption("MaxOpenShort",0);//
SetOption("RefreshWhenCompleted",False);//
SetOption("ExtraColumnsLocation",False);//
//SetOption("SettlementDelay",0);//
//OptimizerSetEngine("cmae");  
SetTradeDelays(0,1,0,0);// 
SetBacktestMode( backtestRegular); 
//SetOption("portfolioreportmode",1);//

Short=0;
Cover=0;

//Historical Vola filter / PositionScore
HV = 100 * StDev(log(C/Ref(C,-1)),100) * sqrt(252);

///////////////////////////////////////////////////////////////
//Buy Condition:
turnover = MA(V,21)*C>10000000;//
oc = NorgateOriginalCloseTimeSeries()>1;
buy1= L<Ref(L,-1) AND Ref(L,-1)<Ref(L,-2) AND Ref(L,-2)<Ref(L,-3) AND C<MA(C,5)  AND C>MA(C,100) AND turnover AND oc  AND constituent  AND NOT OnSecondLastBarOfDelistedSecurity;// AND Ref(L,-3)<Ref(L,-4) ;

/////////////////////////////////////////////////////////////////

Buy=				Ref(buy1,-1) AND limit    ;
Sell=				C>Ref(C,-1) OR OnSecondLastBarOfDelistedSecurity;
Buy = 				ExRem(Buy,Sell); 
Sell = 				ExRem(Sell,Buy); 

//Filter = buy1;
//AddColumn(buy1,"Buy");//
//AddColumn(HV,"HV");
positionScore=		mtRandom();//HV; //rank by HV or try mtRandom()
 
Plot(limit*2,"limit",colorRed,styleLine);
Plot(Ref(buy1,-1),"buy",colorgreen,styleLine);

if I snip this:
image
image

I'm concerned about this line:

turnover = MA(V,21)*C>10000000;//
oc = NorgateOriginalCloseTimeSeries()>1;
buy1= L<Ref(L,-1) AND Ref(L,-1)<Ref(L,-2) AND Ref(L,-2)<Ref(L,-3) AND C<MA(C,5)  AND C>MA(C,100) AND turnover AND oc  AND constituent  AND NOT OnSecondLastBarOfDelistedSecurity;// AND Ref(L,-3)<Ref(L,-4) ;

/////////////////////////////////////////////////////////////////

Buy=				Ref(buy1,-1) AND limit    ;
Sell=				C>Ref(C,-1) OR OnSecondLastBarOfDelistedSecurity;
Buy = 				ExRem(Buy,Sell); 
Sell = 				ExRem(Sell,Buy); 

If I change this to:

///////////////////////////////////////////////////////////////
//Buy Condition:
turnover = MA(V,21)*C>10000000;//
oc = NorgateOriginalCloseTimeSeries()>1;
buy1= L<Ref(L,-1) AND Ref(L,-1)<Ref(L,-2) AND Ref(L,-2)<Ref(L,-3) ;

/////////////////////////////////////////////////////////////////

Buy=				Ref(buy1,-1) AND limit  AND C<MA(C,5)  AND C>MA(C,100) AND turnover AND oc  AND constituent  AND NOT OnSecondLastBarOfDelistedSecurity  ;
Sell=				C>Ref(C,-1) OR OnSecondLastBarOfDelistedSecurity;
Buy = 				ExRem(Buy,Sell); 
Sell = 				ExRem(Sell,Buy); 

it looks like this:
image

still no-where near....

so last modification:
(more stringent entry)

#include_once "Formulas\Norgate Data\Norgate Data Functions.afl"
OnSecondLastBarOfDelistedSecurity = !IsNull(GetFnData("DelistingDate")) AND (BarIndex() == (LastValue(BarIndex()) -1) OR DateTime() >= GetFnData("DelistingDate") ) ;
OnLastTwoBarsOfDelistedSecurity = !IsNull(GetFnData("DelistingDate")) AND (BarIndex() >= (LastValue(BarIndex()) -1) OR DateTime() >= GetFnData("DelistingDate") );

index_string1 ="$RUA";
constituent = NorgateIndexConstituentTimeSeries(index_string1);

//Max. Pos. Held
qty=							10;//Optimize("qty",30,10,70,5);// 

//Limit-Entry
lower = Optimize ("limit",1.5,0.5,2.6,0.2); //<-------------------more stringent entry looks better
limit = L<=Ref(C,-1)-lower*Ref(ATR(10),-1);
slippage=1;
BuyPrice=						Ref(C,-1)-lower*Ref(ATR(10),-1); // simulate Limit Price
SellPrice=						O;//no slippage //with slippage O-Ref(ATR(10),-1)*slippage/100;
PctSize =    					100/qty; //simple % alloc.

SetPositionSize(PctSize, spsPercentOfEquity);  

//Back-Tester Initial Settings
//SetBarsRequired(200);//
SetOption("NoDefaultColumns",0);//
SetOption("InitialEquity",100000);//
SetOption("AllowSameBarExit",false);//
SetOption("ActivateStopsImmediately",false);//TRUE when trading on OPEN
SetOption("AllowPositionShrinking",False);//
SetOption("FuturesMode",True);//
SetOption("AccountMargin",100);//
SetOption("InterestRate",0);//
SetOption("maxopenpositions",qty);//
//EnableRotationalTrading()
//SetOption("WorstRankHeld",false);// rot trading only
SetOption("MinShares",1);//
SetOption("MinPosValue",0);//
SetOption("PriceBoundChecking", False);//
SetOption("CommissionMode",3);// 0 - use portfolio manager commission table 1 - percent of trade 2 - $ per trade 3 - $ per share/contract
SetOption("CommissionAmount",0.01);//
SetOption("ReverseSignalForcesExit", False);//
SetOption("UsePrevBarEquityForPosSizing", True);//
SetOption("PortfolioReportMode", 0);// 0: trade list 1: detailed log 2: summary 3: no output
SetOption("UseCustomBacktestProc",False);//
SetOption("EveryBarNullCheck",False);//
SetOption("HoldMinBars",0);//
SetOption("EarlyExitBars",0);//
SetOption("EarlyExitFee",0);//
SetOption("EarlyExitDays",0);//
SetOption("DisableRuinStop",True);//
//SetOption("GenerateReport",1)
SetOption("SeparateLongShortRank",False);//
//SetOption("MaxOpenLong",0);// 
//SetOption("MaxOpenShort",0);//
SetOption("RefreshWhenCompleted",False);//
SetOption("ExtraColumnsLocation",False);//
//SetOption("SettlementDelay",0);//
//OptimizerSetEngine("cmae");  
SetTradeDelays(0,1,0,0);// 
SetBacktestMode( backtestRegular); 
//SetOption("portfolioreportmode",1);//

Short=0;
Cover=0;

//Historical Vola filter / PositionScore
HV = 100 * StDev(log(C/Ref(C,-1)),100) * sqrt(252);

///////////////////////////////////////////////////////////////
//Buy Condition:
turnover = MA(V,21)*C>10000000;//
oc = NorgateOriginalCloseTimeSeries()>1;
buy1= L<Ref(L,-1) AND Ref(L,-1)<Ref(L,-2) AND Ref(L,-2)<Ref(L,-3) ;

/////////////////////////////////////////////////////////////////

Buy=				Ref(buy1,-1) AND limit  AND C<MA(C,5)  AND C>MA(C,100) AND turnover AND oc  AND constituent  AND NOT OnSecondLastBarOfDelistedSecurity  ;
Sell=				C>Ref(C,-1) OR OnSecondLastBarOfDelistedSecurity;
Buy = 				ExRem(Buy,Sell); 
Sell = 				ExRem(Sell,Buy); 

//Filter = buy1;
//AddColumn(buy1,"Buy");//
//AddColumn(HV,"HV");
positionScore=		mtRandom();//HV; //rank by HV or try mtRandom()
 
Plot(limit*2,"limit",colorRed,styleLine);
Plot(Ref(buy1,-1),"buy",colorgreen,styleLine);

this looks like it's a little closer:
image
image

B U T : This is clearly wrong and induces a future leak as the close can't be known in advance.

Buy=				Ref(buy1,-1) AND limit  AND C<MA(C,5)  AND C>MA(C,100) AND turnover AND oc  AND constituent  AND NOT OnSecondLastBarOfDelistedSecurity  ;

Thanks for your replies.

@Dionysos you are still using the limit test in your Buy logic. I know for a fact that @CesarA would not test that way, but rather would use a CBT so that if there are N open slots today, he will only consider the top N Setups rather than picking the top N Entries (i.e. Setups that trigger the limit price). And as you have pointed out, you have future leaks because you are entering intraday at the limit price, but checking the entry day's closing price against the entry day's MA(5) and MA(100).

2 Likes

I get what you mean, but in the posts that I read, Cesar tested with PositionScore = mtRandom()
The average CAR was 26%, I do get similar results with future leak and no commission. I'll have a look again, but so far I'll give it a pass, I'm not sure what I'm doing wrong.
Most of the Mean Reversion ideas took quite a beating after 2015, went flat or are somewhat in a drawdown. Hence trying to throw in limit orders was an idea that may have helped, so I thought.

@mradtke, I was testing someone else's idea. That is why the positionscore is set to random. In my blog post I then run a Monte Carlo analysis. I did use the CBT but probably don't need to in this case

@Dionysos, a quick look at the code I see two more issues. In the line below you are not taking into account opens below the limit price.

Also, I am not removing excess signals. So the ExRem I did not use. And I use
SetBacktestMode( backtestRegularRaw);

5 Likes

Ah! Thanks for your help. Much appreciated. Let me give it a shot.

@CesarA
Unbelievable! It did the trick! comms are 0.01 per share and slippage is 1% of ATR(10).
Thanks for the pointer :slight_smile:

lower = Optimize ("limit",0.5,0.5,2.6,0.2);
limit = L<=Ref(C,-1)-lower*Ref(ATR(10),-1);
slippage=1;
BuyPrice=						Min(O,Ref(C,-1)-lower*Ref(ATR(10),-1)); // simulate Limit Price
SellPrice=						O-Ref(ATR(10),-1)*slippage/100;

and

SetBacktestMode( backtestRegularraw); 

image
image

4 Likes

Apologies, I missed the part in the original blog post that stated you have the ability to place an unlimited number of orders, and you (or your automated system) watch the market all day so that you can cancel all remaining orders after you've reached your Max Open Positions. For that type of scenario, you don't need a CBT. For those of us that don't have unlimited capital or an automated trading system that places orders only when the stock price hits the limit price, you do need a CBT.

1 Like