Rotation of 5 Momentum stocks (like Clenow)


#21

I gave up on rotational backtest mode. There are a couple of things I’m facing difficulties with. As a result, and in need of forcing individual position exits, I used the regular backtest mode, but to create a ranking with “STATICVARGENERATERANK”.

here is how it goes:

all symbols go into watchlist 0. Leave the index out of this watchlist.

if I run my analysis several times, my results vary. I have no “random()” in my code so this is a surprise.

If someone would be able to help me understand this irregularity, it’s be great.

You can provoke the error by going to line 101 and change “stocknum” to another value, say 1, 2, 3, 4, you choose.
Then you send the code to AA and click backtest a couple of times.
It can be observed that the results change. Why?

/* System Description:
As described by Clenow, Stocks on the Move, 2015
A very fine read, go n get it.

1. Market regime filter:
Snp500Index > SMA(C,200)

2. Ranking stocks:
2.1 Exponential regression slope over the past 90 days, annualized to 250 trading days - Unit [%]
2.2 Ensure smoothness of slope by calculating the coefficient of determination, R2 ("R Squared") - Value Range [0: worst fit ... 1: best fit]
2.3 Gauge for momentum: 2.1 * 2.2

3. Stock volatility consideration: ATR(20)

4. Additional Filter 1: Individual stock needs to trade above the 100 day SMA - C > SMA(C,100)
5. Additional Filter 2: Avoid stocks with gaps - no move larger than 15% over the past 90 days

6. Position Sizing: Risk Parity Approach - #ofshares = (Equity * RiskFactor) / ATR(20)
	Suggested 0.1% daily impact on total equity: Equity * 0.001 / ATR(20)
	
7. Open position Rebalancing: Adjust position size to current ATR(20) if threshold "thr" exceeds a value. Rebalance 1 or 2 times per month.

8.1 Exits - Portfolio Rebalancing: Close below 100 day SMA -  C<SMA(C,100)
8.2 Exits - Portfolio Rebalancing: Stock no longer in the top 20% ranking (SnP500 = 500 stocks, top 20% = drops out of the best performing 100 stocks)
8.3 Exits - Portfolio Rebalancing: Stock dropped out of index

Logic:

* Buy as many stocks as your Equity allows, each pos has a suggested daily impact of 0.1% of portfolio
* prefer the highest ranked until out of cash
* no buy: GAP >15%
* no buy: C<SMA(C,100)
* no buy: SnP500, close < SMA(SnP500,close, 200)

* rebalance position 1 or 2 times per month

* rebalance portfolio on Wednesday: (i.e. exit or open new pos) based on top20%, close below 100day SMA, or gap larger than 15%, or drop out of index

*/


PositionRisk = 		1; 														// allocate 0.1% of equity on mulit*ATR(20) range
filterperiod = 		200;//Optimize("Regime Filter Period",200,10,250,10);//		// Market Regime Filter Period
ExpRegperiod = gapperiod = 		90;//Optimize("Mom + R2 period",90,50,200,10);				// Exp Reg Period
//gapperiod = 		Optimize("Gap Period",90,5,100,1);						// lookback period for gaps
gapsize = 			15;//Optimize("Gap Size %",15,5,30,1);						// gapsize in %
maperiod = 			100;//Optimize("MA-period",100,20,200,5);
rankheld =			100;//Optimize("Rank Held",100,20,200,5);


RiskPerContract = ATR( 20 );
PctSize =  PositionRisk /  RiskPerContract; 

SetOption("maxopenpositions",100);//
SetPositionSize( PctSize, spsPercentOfEquity );

//SetBarsRequired(200);//
SetOption("NoDefaultColumns",True);//
SetOption("InitialEquity",100000);//
SetOption("AllowSameBarExit",True);//
SetOption("ActivateStopsImmediately",true);//TRUE when trading on OPEN
SetOption("AllowPositionShrinking",False);//
SetOption("FuturesMode",false);//
SetOption("AccountMargin",100);//
SetOption("InterestRate",0);//

SetOption("MinShares",1);//
SetOption("MinPosValue",0);//
SetOption("PriceBoundChecking", False);//
SetOption("CommissionMode",1);// 0 - use portfolio manager commission table 1 - percent of trade 2 - $ per trade 3 - $ per share/contract
SetOption("CommissionAmount",0.1);//

SetOption("ReverseSignalForcesExit", False);//
SetOption("UsePrevBarEquityForPosSizing", True);//
SetOption("PortfolioReportMode", 0);// 0: trade list 1: detailed log 2: summary 3: no output
//SetOption("UseCustomBacktestProc",true);//
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",100);// 
//SetOption("MaxOpenShort",0);//
SetOption("RefreshWhenCompleted",False);//
SetOption("ExtraColumnsLocation",False);//
//SetOption("SettlementDelay",0);//
//OptimizerSetEngine("cmae");  
SetBacktestMode( backtestRegular); 
SetTradeDelays(1,1,1,1);// 
BuyPrice=O; 
SellPrice=O; 

   
wlnum = 0;//GetOption( "FilterIncludeWatchlist" );
List = CategoryGetSymbols( categorywatchlist, wlnum ) ;


if( Status("stocknum")==2)
{
StaticVarRemove( "ValuesToSort*" ); 
for( n = 0; ( Sym = StrExtract( List, n ) )  != "";  n++ )
{ 
    SetForeign( sym ); 
    ann_slope = ((exp(LinRegSlope(ln(C),ExpRegperiod))^250) - 1)*100;
	R2 = (Correlation(Cum(1), C, ExpRegperiod))^2;
	momentum = R2 * ann_slope;//
    RestorePriceArrays(); 
    StaticVarSet( "ValuesToSort" + sym, momentum ); 
} 

// perform ranking 
StaticVarGenerateRanks( "rank", "ValuesToSort", 0, 1234 ); // normal rank mode 
}

rank=StaticVarGet( "RankValuesToSort" + Name() );//



//Market Regime Filter
index_close = Foreign("^GSPC","C");
index_filter = index_close > MA(index_close,filterperiod);


//Gap Filter
upgap=IIf(L-Ref(H,-1)>0,((L-Ref(H,-1))/L)*100,0);//
downgap=IIf(H-Ref(L,-1)<0,((Ref(L,-1)-H)/H)*100,0);//
gap= Max(HHV(upgap,gapperiod),HHV(downgap,gapperiod));//
gapok=gap<gapsize;//

//Momentum Calc, based on exponential regression slope * R2;//
ann_slope = ((exp(LinRegSlope(log(C),ExpRegperiod))^250) - 1)*100;
R2 = (Correlation(Cum(1), C, ExpRegperiod))^2;
momentum = R2 * ann_slope;//

//Trade once a week only
TradingDay = DayOfWeek()==3;

//Allow positions only when C above MA
MA_exit = C<MA(C,maperiod);//
MA_entry = C>MA(C,maperiod);//

score = Null;
exit = Null;
noentry = Null;

for( i = 200; i < BarCount; i++ )
{
	//Handle score = PSEUDO_scorenorotate
	if (Tradingday [i] == False)
		{
			score [i] = 0;
			
		}	
		
	if (Tradingday [i] == True AND index_filter [i] == false)
		{
			score [i] = 0;
			
		}
	// Handle score = 0 first
	if (Tradingday [i] == True AND MA_exit [i] == True)
		{
			//exit [i] = 1;
			//noentry [i] = 1;
			score[i] = -1;//
			
		}
	if (Tradingday [i] == True AND momentum [i] <= 0)
		{
			//exit [i] = 1;
			//noentry [i] = 1;
			score[i] = -1;//
		}
	if (Tradingday [i] == True AND gapok [i] == False)
		{
			//exit [i] = 1;
			//noentry [i] = 1;
			score[i] = -1;//
		}	
		
		
	// assign score			
	if (Tradingday[i] == True AND momentum [i] > 0 AND index_filter [i] == True AND MA_entry[i] == true)
		{
			score [i] = momentum [i];
			
		}
	else(score[i]=Null);
	
}
PositionScore = score;//
Buy=	score>0 AND rank < rankheld;
Sell= 	score==-1 OR  rank> rankheld;//


Plot(rank," ", colorRed,styleline);




Any ideas?

Dio


#22

Maybe this is misleading:
If stocknum is changed, of course, the results change. But they should change only once: after changing stocknum.
If you leave stocknum now untouched and click “backtest” in AA multiple times (say 10 times) the result obviously should be the same. But it keeps changing.

Any ideas why?

Dio


#23

I still think this is were the problem comes from but I need to understand better why:

Symbols in my watchlist may have different starting quotes: some quotes may be available as early as 1995 and others become available after 2000 only.

if( Status("stocknum")==2)
{
StaticVarRemove( "ValuesToSort*" ); 
for( n = 0; ( Sym = StrExtract( List, n ) )  != "";  n++ )
{ 
    SetForeign( sym ); 
    ann_slope = ((exp(LinRegSlope(ln(C),ExpRegperiod))^250) - 1)*100;
	R2 = (Correlation(Cum(1), C, ExpRegperiod))^2;
	momentum = R2 * ann_slope;//
    RestorePriceArrays(); 
    StaticVarSet( "ValuesToSort" + sym, momentum ); 
} 

// perform ranking 
StaticVarGenerateRanks( "rank", "ValuesToSort", 0, 1234 ); // normal rank mode 
}

rank=StaticVarGet( "RankValuesToSort" + Name() );//

#24

I figured out why my results kept changing.
There were some symbols in my database with too little quotes.
That messed up the ranking. TJ has a cleanup script that can be modified to delete empty tickers.

My code still needs some clean-up and some exception handling etc. But otherwise I like the fact that “backtestmoderegular” can be used, it also performs fast enough to my liking. That way also “sell” can be assigned neatly, the codes above with “backtest rotational” handle this differently.

Lastly, there still needs to be a custom backtest procedure for re-balancing + add/take out money (at least a backtest with + without) and proper handling of survivorship bias with adequate historical index constituents lists. Otherwise results will be overly optimistic.

Dio


#25

@Dionysos

I like your effort to overcome all the issues that you are facing learning how to develop a certain strategy.

And I’m sure that when you are done, you’ll still find a way to improve it, simplify, etc.

…proper handling of survivorship bias with adequate historical index constituents lists. Otherwise, results will be overly optimistic.

IMHO it is very important to put an extra effort on this last part: if anyone plans to actually use a certain strategy with real money there is the need to backtest it with high-quality stock market data and realistic commissions and slippage costs (with a bit of extra margin).

Otherwise, the results may be significantly biased, as you wrote, probably overly optimistic!


#26

That is precisely the reason why “Pad and align to reference symbol” exists. All rotational and ranking systems rely on perfect alignment. So your safest bet is to turn this option on and pick reference symbol that has quotes for all trading days in analysed period (such as market index).


#27

Speaking of which… Can you suggest a data provider for EOD data other than Norgate that offers historical constituent(s lists) for US stocks?
I’m getting itchy fingers with Norgate because they keep on postponing the release date for their new data tool, hence new subscriptions are temporarily blocked.

Dio


#28

Unfortunately no. Some alternatives were discussed in this thread (and more in general in the “Data Sources” forum section), but I suppose you already saw that…

In any case, keep in mind that market is always there… some extra months of waiting and testing will do no harm in the long run!
And for some initial strategy validation, you can always subscribe to the service that offers trial periods. (Nortgate does).


#29
/*

* Coded by "Dionysos" , "Dio", Matthias Kuschel 3rd of Dec 2017
* no guarantees 
* some variables are still in there because I coded a CBT scaling in/out procedure, in addition to a withdraw/add-funds procedure (which I will not disclose)
* Scaling in/out reduces CAR as it's a defensive measure, may improve CAR/MDD
* Market regime filter is my own twist based on Donchian Channel, improved robustness based on my perception

My code has undergone some important changes. 
Posting non-sense code (ignore the previous ideas, = flawed) is of little help, 
but it was caused by the fact the ranking mechanism had the above stated "random" element which needed to be addressed first (ignore the previous code in the thread, use the following one). 
**The solution to the problem is to have no stocks inside the watchlist with less than 3 bars data**. 
This is in addition to what what TJ said with "PAD AND ALIGN TO REFERENCE SYMBOL". 
You *will* get random results if your database contains symbols with less than 3 bars data as the ranking function will fail you. 
Make sure you clean up your database. 
(Suggestion: use TJ's cleanupscript in "SCRIPTS" folder, modify it so it will delete all empty tickers without warning. Helps a great deal if dealing with 500+++ symbols.)


How to deal with the code:
1. "Pad and align data" needs to be checked
2. AA settings need to be set to "daily" and "long"
3. Other settings are defined in the code by overwriting the AA-settings
4. Trades are entered on "CLOSE" with "DELAYS = 0" (add artificial future bar is NOT realtime trading compatible IMHO)
5. wlnum is the watchlist where all your trading universe is

Points to consider:
1. I ran the system on S&P500 stocks and NASDAQ100 stocks, data from yahoo: testing only - trading a bad idea.
2. This test needs to be made survivorship bias free to avoid overly optimistic results.
3. 2.) can be achieved by using NORGATE data with historical constituents lists of the index which is currently (2nd Dec 2017) unavailable due to upgrading of their interface. I hope they get their act together and let me sign up soon. Custom lists can be used, but are troublesome to maintain. Include custom lists at your own preference.
4. Stocks that stop trading are not reflected in the backtest, trades need to be closed which this code does currently not include. Use your own solution.
5. The system logic is robust and works over a wide range of parameters (very nice!)
6. Trading US-stocks live generates comms as in "dollars per share". Due to splits and div adjustmenst backtesting this method is greatly flawed using "dollar-amount" commission settings. To correct this, use comms as in %-of trade volume. We make an estimate for comms in the code (as % of trade volume).
7. The system estimates a fair amount of slippage as % of ATR(5) and deducts it from the "Sell-Close" and "Buy-Close", reflected in "buyprice".
8. The *only data provider* that currently supports historical constituents for a retail-trader in a ready-to-use-solution in an affordable way for stocks is NORGATE. I should get 2 years of data for free for saying this. However, it means for you as a user, you are a beggar and have no other options left in your pocket if they break-down, fail, or go out of business or start doing funny things you don't like. They have been around for a while. I love what they do and would vouch for them.
9. The system is written in such a way that AA is used during live trading and new trades are signaled in AA window. Click "Backtest" every day after importing data and act on the signals.
10. If you understand that *EVERY* trader wants to have a CAR/MDD>1, understand that Warren has achieved his current status with CAR/MDD ~0.3, you are in a world of realistic trading expectations and become prone to sticking with your methodology because you have no unrealistic expectations.

*/

RiskPerContract = 				ATR( 20 );
slippage_percent = 				5;
slippage = 						ATR(5)*slippage_percent/100;
PositionRisk = 					0.1;//Optimize("posrisk",0.1,0.02,0.2,0.02); // allocate 0.1% of equity on mulit*ATR(20) range
filterperiod = 					150;//Optimize("Regime Filter Period",120,10,250,10);//		// Market Regime Filter Period
ExpRegperiod = gapperiod = 		90;//Optimize("Mom + R2 period",90,50,200,10);				// Exp Reg Period
//gapperiod = 					Optimize("Gap Period",90,5,100,1);						// lookback period for gaps
gapsize = 						15;//Optimize("Gap Size %",15,5,30,1);						// gapsize in %
maperiod = 						100;//Optimize("MA-period",90,20,250,10);
rankheld =						100;//Optimize("Rank Held",100,20,200,10);
Scaleday = 						Month() != Ref(Month(),-1);//

reportswitch = 					0;/// 0 trade log, 1 detailed trades including scale operations
CBTswitch =						0;// 0 skip CBT, 1 enable CBT

wlnum = 						0;// select watchlistnumber to run the ranking on. select the same in "FILTER SETTING"
stocknumber =					3;// identify a stock with the max history required for the backtest (select stock that dates back the longest)

StaticVarSet(Name() + "ATR",RiskPerContract);//
StaticVarSet(Name() + "slippage",slippage);//
StaticVarSet(Name() + "Entry",C);
StaticVarSet(Name() + "scaleday", scaleday);// 	

BuyPrice = 						Close + slippage; 
SellPrice = 					Close - slippage; 

PctSize =    					PositionRisk * Buyprice / (RiskPerContract);//

money_in_date=					DateNum()==1160804;//
money_in=						1000000;//







SetOption("maxopenpositions",1000);//
SetPositionSize( PctSize, spsPercentOfEquity );
SetOption("NoDefaultColumns",True);//
SetOption("InitialEquity",100000);//
SetOption("AllowSameBarExit",false);//
SetOption("ActivateStopsImmediately",false);//TRUE when trading on OPEN
SetOption("AllowPositionShrinking",false);//
SetOption("FuturesMode",false);//
SetOption("AccountMargin",100);//
SetOption("InterestRate",0);//
SetOption("MinShares",1);//
SetOption("MinPosValue",10);//
SetOption("PriceBoundChecking", False);//
SetOption("CommissionMode",1);// 0 - use portfolio manager commission table 1 - percent of trade 2 - $ per trade 3 - $ per share/contract
SetOption("CommissionAmount",0.1);//
SetOption("ReverseSignalForcesExit", False);//
SetOption("UsePrevBarEquityForPosSizing", false);//
SetOption("PortfolioReportMode",reportswitch);// 0: trade list 1: detailed log 2: summary 3: no output
SetOption("UseCustomBacktestProc",CBTswitch);//
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",100);// 
//SetOption("MaxOpenShort",0);//
SetOption("RefreshWhenCompleted",False);//
SetOption("ExtraColumnsLocation",False);//
//SetOption("SettlementDelay",0);//
//OptimizerSetEngine("cmae");  
SetBacktestMode( backtestRegular); 
SetTradeDelays(0,0,0,0);// 


List = CategoryGetSymbols( categorywatchlist, wlnum ) ;

if( Status("stocknum")==stocknumber)
{

StaticVarRemove( "valuestosort*" );
//StaticVarRemove( "rankvaluestosort*" );
for( n = 0; ( Sym = StrExtract( List, n ) )  != "";  n++ )
{ 
    SetForeign( sym); 
    ann_slope = ((exp(LinRegSlope(ln(C),ExpRegperiod))^250) - 1)*100;
	R2 = (Correlation(Cum(1), C, ExpRegperiod))^2;
	momentum = R2 * ann_slope;//
    RestorePriceArrays(); 
    StaticVarSet( "valuestosort" + sym, momentum ); 
} 
// perform ranking 
StaticVarGenerateRanks( "rank", "valuestosort", 0, 1234 ); // normal rank mode 1234 mode to assign unique rank (as opposed to mode 1224)

}


//Market Regime Filter
index_close = Foreign("^GSPC","C");
//index_filter = index_close > MA(index_close,filterperiod);
DonchianUpper = HHV(Ref(index_close ,-1),filterperiod);
DonchianLower = LLV(Ref(index_close ,-1),filterperiod);

index_filter=Flip(index_close>DonchianUpper,index_close<DonchianLower);//

//Gap Filter
upgap=IIf(L-Ref(H,-1)>0,((L-Ref(H,-1))/L)*100,0);//
downgap=IIf(H-Ref(L,-1)<0,((Ref(L,-1)-H)/H)*100,0);//
gap= Max(HHV(upgap,gapperiod),HHV(downgap,gapperiod));//
gapok=gap<gapsize;//

//Momentum Calc, based on exponential regression slope * R2;//
ann_slope = ((exp(LinRegSlope(log(C),ExpRegperiod))^250) - 1)*100;
R2 = (Correlation(Cum(1), C, ExpRegperiod))^2;
momentum = R2 * ann_slope;//

//Trade once a week only
TradingDay = DayOfWeek()==3;

//Allow positions only when C above MA
MA_exit = C<MA(C,maperiod);//
MA_entry = C>MA(C,maperiod);//

/*
// Stock stops trading exit
bi = BarIndex();
lastbi = LastValue( bi ) - Status("BuyDelay"); 
exitLastBar = bi == lastbi;

*/

score = Null;
rank=StaticVarGet( "rankvaluestosort" + Name() );//
for( i = 0; i < BarCount; i++ )
{
	//Handle score = PSEUDO_scorenorotate
	if (Tradingday [i] == False)
		{
			score [i] = 0;
		}	
		
	if (Tradingday [i] == True AND index_filter [i] == 0)
		{
			score [i] = 0;
		}
	// Handle Exits
	if (Tradingday [i] == True AND MA_exit [i] == True)
		{
			score[i] = -1;//
		}
	if (Tradingday [i] == True AND momentum [i] < 0)
		{
			score[i] = -1;//
		}
	if (Tradingday [i] == True AND gapok [i] == False)
		{
			score[i] = -1;//
		}	
	if(Tradingday [i] == True AND rank[i]>rankheld)
		{
			score[i] = -1;//
		}
	/*if(exitLastBar[i] == True)
		{
			score[i] = -1;
		}*/
		
	// assign score			
	if (Tradingday[i] == True AND index_filter [i] == True AND MA_entry[i] == True AND gapok[i]== True AND momentum [i] > 0)//
		{
			score [i] = momentum [i];
		}
}


PositionScore = score;//
Buy=	score > 0 AND rank < rankheld;
Sell= 	score == (-1) ;//

//Plot(index_filter,"indexfilter",colorRed,styleLine);//
Plot(rank," ", colorRed,styleline);
Plot(score,"score",colorRed,styleLine);


Dio


#30

image

pretty stable among a large set of parameters (results include scaling in/out)

image

image


SetOption() - Pad and Align
#31

@Tomasz, I wonder why “Pad And Align” is not available as a SetOption().
IMHO it would be better to have the possibility to turn it on (overriding the eventual default setting) only when a formula will actually require it, using the value param to pass a symbol that could be specific to a certain formula.


#32

Because padding is done BEFORE your formula executes.


#33

Thanks for the fast answer. Then maybe it could be considered for a #pragma.


#34

I just wanted to add my code for Clenow's momentum strategy in the hopes of keeping the conversation going on this topic. CAR is about 6% with a max drawdown of 32% if backtested on the current S&P400 Midcap universe starting in 2005. Decent system, not great.

I found that using simple fixed fraction position-sizing worked better than using his risk-sizing/ATR.

If backtested on a larger universe of stocks (e.g. Russel 2000) , the performance is much poorer but likely more representative of how this strategy will perform going forward. In other forums (not Amibroker), I've seen users backtest Clenow's system on a list of the current S&P500 constituents. Doing so introduces a data snooping bias because the stocks that are in the S&P500 today got there by growing tremendously over the past ten years. To accurately backtest this strategy on cap-weighted index (like the S&P500), you'd need to have a historical daily record of all of the index's constituents. I don't know how to do that in Amibroker; moreover, I couldn't find such a list anywhere online without having to pay a significant amount for it.

If anybody has come up with any modifications or improvements to this strategy, I would love to hear about them. A lot of people in the Quantopian community have chimed in on a post on their forum with tweaks and enhancements. Having said that, most of the improvements that they made seemed to be due to curve-fitting and/or something that didn't pass my gut check.

Anyways, here's my take on it:

// Parameters
numPositions = 10; 
numExtras = 2; 
capital = 50000;
lookBack = 75; 
pctDeltaThresh = 0.15; //Optimize("pctDeltaThresh", 0.15, 0.02, 0.20, 0.02);
//riskFactor = 0.00164; //Optimize("riskFactor", .001, .0001, .02, .0001);

// Small Calcs required for backtest settings
worstHeld = numPositions + numExtras;

// Backtest Settings
SetBacktestMode( backtestRotational );
SetOption("InitialEquity", capital); 
SetOption("MaxOpenPositions", numPositions);
SetOption("WorstRankHeld", worstHeld);

// Position Sizing
// 1. Based on ATR (Clenow's method).
//symbolATR = ATR(20);
//numShares = (riskFactor * (capital))/symbolATR;
//SetPositionSize(numShares, spsShares);

// 2. Fixed fraction 
SetPositionSize( 100/numPositions, spsPercentOfEquity );

//********************************************************************************************

// 1. exponential regression to get slope, past number of days specified by lookBack parameter
slopeArray = ln(Close);
linSlope = LinRegSlope(slopeArray, lookBack);
eSlope = exp(linSlope);


// 2. annualize slope = (1 + slope)^250
annualSlope = (eSlope^250)-1;


// 3. calculate R2 (coefficient of determination) of the exponential price regression line
bi = BarIndex();
R2 = (Correlation(slopeArray, bi, lookBack))^2;


//4. Multiply annualized slope by R2 to get volatility-adjusted annualized slope
adjSlope = R2 * annualSlope;

// 5. filter out stocks trading below their 100d moving average
SMA = MA(C, 100);
belowSMAFlag = Close < SMA; // 1 -> don't trade it

// 6. filter out all trades if S&P500 is below its 200d moving average
foreignSPYClose = Foreign("SPY", "C", FixUp = True); 
bullMarketFlag = foreignSPYClose > MA(foreignSPYClose, 200); 

// 7. Filter out stocks that had a close-close percent change > 15%
largeClosePd = HHV(abs(Close - Ref(Close, -1))/Ref(Close, -1), lookBack) >= pctDeltaThresh;

// Calculate final compScore including all flags
compScore = IIf(belowSMAFlag == 1, 0, Max(adjSlope, 0));
compScore = IIf(bullMarketFlag == 1, Max(compScore, 0), 0);
compScore = IIf(largeClosePd == 1, 0, Max(compScore, 0));

// Evaluate after Friday's adjusted close data becomes available, trade at open on Monday
bestDay = 5; 
rotation = DayOfWeek() == bestDay;
PositionScore = IIf(rotation == 1, compScore, scoreNoRotate);

//end

#35

Some thoughts:

  1. CAR/MDD way too low for me
  2. no info on commissions
  3. no regards to survivorship bias
  4. re-balancing reduces performance even more

Flaws in the logic:

  1. Your code does not reflect: "Sell when the rank drops below 100", you essentially sell after the rank drops below 12. That way you lose a lot of performance. Your strategy resembles the one that Clenow advertises only remotely, maybe 70%.
  2. You only open 10 positions, it seems like a short cut, but not the real-deal.

Also the position sizing algo as "fixed %" has the problem that some volatile stocks will either be "performance leader", or "performance killer". I'd really go for risk parity, yet change total risk when market conditions favor this strategy (i.e. after a 30% drop of the baseline index, S&P500).

It's my 2 cents.

Long story short for you:
Selling stocks immediately after the rank drops below 12 (as in your code) ruins your performance.

Copy past my code snippet from above, set your watchlist correctly and make sure you have no tickers with empty quotes. Adjust all settings, you'll be surprised :slight_smile:
Cheers.
Dio


#36

the way you code it, IMHO not worth starting at all.

:slight_smile:


#37

hi Dionysos,
You have done a great work on creating the code. I hope I can modify your code to my personal use.
But let me ask you something.

On the part that you code about DonchianUpper and DonchianLower.
Should it be like this instead...

DonchianUpper = Ref(HHV(C, filterperiod), -1); 
DonchianLower = Ref(LLV(C, filterperiod), -1);  

I am novice on coding, I may be wrong.


#38

Just to share: I wanted to try a rotational system that traded every two weeks instead of every week. I found the required code in the discussion above but it wouldn't work for me. After reading more in the discussion, I realized that you have to have the Pad and align turned on for the bi-weekly code to work.


#39
DonchianUpper = Ref(HHV(C, filterperiod), -1); 
DonchianLower = Ref(LLV(C, filterperiod), -1);  

Hi,
thanks for the feedback.

long story short: it's the same. Just try both versions: your suggestion and the one I outlined, like this:

pds=Param("period",100,1,300,1);//

DonchianUpper =HHV(Ref(C,-1),pds);
DonchianLower = LLV(Ref(C,-1),pds);

DonchianUpper1 = Ref(HHV(C, pds), -1); 
DonchianLower1 = Ref(LLV(C, pds), -1);  

Plot(DonchianUpper,"DU"+ _PARAM_VALUES(),colorBlue,styleLine);
Plot(DonchianLower,"DL"+ _PARAM_VALUES(),colorBlue,styleLine); 
Plot(DonchianUpper1,"DU"+ _PARAM_VALUES(),colorRed,styleLine);
Plot(DonchianLower1,"DL"+ _PARAM_VALUES(),colorred,styleLine); 

image


#40

With regards to Pad and align and all other settings available from UI only:
you should save your PROJECT files (APX) files. They include formula AND SETTINGS. Then you load the PROJECT file, not the formula alone. And once project is loaded you are sure that BOTH formula AND settings are correct and you don't need to worry about typing every setting in the code.