Clenow revisited - R², "Advanced" indicator for Rotation; Sizing Positions


I need to start my very first post with apologies:
Although there are already multiple discussions about indicators and strategies presented in Andreas Clenow's excellent book "Stocks on the Move", I have decided to open a new topic.
My three main reasons to do so are:

  1. egoism - I got many ideas from the excellent discussion Rotation of 5 Momentum Stocks (like Clenow), but want to move discussion to a somewhat different direction.
  2. a different approach - I get Andreas' ideas somehow different than others do, including existing topics. Maybe someone is interested to follow these approaches.
  3. Code confusion - maybe due to the fact that I am totally noob to AmiBroker, but I got confused by multiple topics discussing the R² coefficient of determination and thus decided to do another implementation.

Let's get started.

Andreas, in "Stocks on the Move", as you'll already know, presented a strategy to somehow rotate stocks in the S&P500 universe, using a momentum indicator, he branded with his name, the "Clenow Momentum".
If you studied his website (he already has opened another one Clenow Research, but for this topic the old one is still the reference), you know that he has a very nice and positive attitude and that this branding comes with some irony.

Later - on this web site - he added an "Advanced" Clenow Momentum, because feedback by readers of the book and contributors to the website had asked for two things:
a) Make trading easier and reduce cost for the weekend/once a month/hooby trader and
b) Adjust the strategy for people, who always want to stay in the market and not use the original system's trend filter (simply S&P500 > MA(200), of course to be replaced with an index representative for the universe you trade).

Hoping to start a discussion with you guys, interested in Andreas' ideas, in this first thread I will provide two pieces of code, the results of which I have backtested with Excel calculations, other chart software, C# code and so on, so that I'm pretty sure, it works:

  1. The "Simple" Clenow Momentum (the original indicator)
    has been provided by others before, so please take this code more educationary, because I have added a huge comment to explain why R-squared (or R²) needs to be calculated the way I am presenting here. Spoiler: When you have calculated the slope of a regression line for, lets say day 445 with a lookback period of 90 days, your existing arrays are useless when it comes to R-squared calculation and I have seen this mistake in multiple topics. You need to calculate R-squared for the 90 days ending with the current day (445) instead, thus calculating squared differences between real quotes and your prediction function as it is on day 445 and as it would have predicted quotes the 90 days ending with today.
    I have done that using a loop, but let's look at the code:
function ClenowMomentum(Days, outMomentum, outAnnSlope, outRsquared)
	Tage = Days;
	x = cm = annSlopePct = rSquared = BarIndex();
	xSq = x^2;
	y = Ln(Close);
	xAvg = MA(x, Tage);
	xSqAvg = MA(xSq, Tage);
	yAvg = MA(y, Tage);
	xy = x * y;
	xyAvg = MA(xy, Tage);
	xAvgSq = xAvg^2;
	xDiff = xSqAvg - xAvgSq;
	xDiff = IIf(xDiff==0, Null, xDiff);
	k = (xyAvg - yAvg * xAvg) / xDiff;
	annSlopePct = 100 * (EXP(250 * k) - 1);
	// test: predicts = yAvg + k * (x - xAvg);
	// produces exactly the same results as using buitlin LinearReg function: predicts2 = LinearReg(y, Tage);
// NOTE: The coefficient of determination, R² is defined (according to as
//       R² = 1 - SUM(residual squares) / SUM(total squares)
//       Where, if p is the predicted value for y, residual[i] = p[i]-y[i] and totalSquare[i] = y[i]-yAvg

//       The problem is, we cannot use previously calculated arrays here, because on each particular day (where we want to calcculate R²),
//       we have another value for k and thus another function that has predicted the recent (Tage) values.
//       Instead of using yesterdays values for k, predicts, residuals, etc, on each day, we need to calculate both sums for the recent Tage days.
//       In other words: For each day we get a new linear regression of log(C), using past Tage days and need to calculate this regression's R²
//       by comparing past Tage days to todays "prediction" function.

// We acheive this by initializing the two sum's arrays with today values and then use a loop to step back Tage days, adding daily values:
	sumTotalSquares = (y - yAvg)^2;
	sumResidualSquares = (y - yAvg - k * (x - xAvg))^2; // remember: predict[i] = yAvg + k * (x[i] - xAvg) => y[i] - predict[i] = y[i] - yAvg - k * (x[i] -xAvg)
	for (lookback = -1; lookback >= 1 - Tage; lookback--)
		deltaTotal = (Ref(y, lookback) - yAvg)^2;
		deltaResidual = (Ref(y, lookback) - yAvg - k * (Ref(x, lookback) - xAvg))^2;
		sumTotalSquares += deltaTotal;
		sumResidualSquares += deltaResidual;
	rSq = 1 - sumResidualSquares / sumTotalSquares;
	cm = annSlopePct * rSq;
//	}

	VarSet(outMomentum, cm);
	VarSet(outAnnSlope, annSlopePct);
	VarSet(outRsquared, rSq);

I have not added Plot commands here, because I want to have several functions in an include file and I am not yet experienced in branching on Status("action") to do multiple tasks in the same file. If you want to test/debug, you could simply do that:

cm = as = rs = 0;
ClenowMomentum(d, "cm", "as", "rs");

printf("cm = %g\n", cm );
printf("as = %g\n", as );
printf("rs = %g\n", rs );
  1. The "Advanced" Clenow Momentum
    The "Advanced" Momentum is simply an average of two "Simple" (or "Original") Momentum indicators with different lookbacks. In the models presented and backtested on his website, Andreas uses 125 and 250 days, because he figured out that these longer lookback worked out better than the original 90 days.
    The systems (he presents two, using 25 and 50 stocks) the use this combined Momentum for ranking, very similar to the original system, while the trend filter is disregarded and could be replaced by a rule like "don't buy a stock when its advanced momentum is below <whatever you like, e.g. 20>, even if its high-ranked".

The calculation is simple; I have added another function using the first one from above, adding the feature of allowing you to use a weight (of first momentum) different from the original 0.5 (maybe a good Optimize candidate):

function ClenowAdvancedMomentum(Days1, Days2, weight1, outAdvancedClenowMomentum, outClenowMomentum1, outAnnualSlope1, outRsquared1, outClenowMomentum2, outAnnualSlope2, outRsquared2)
	SetBarsRequired(Max(Days1, Days2));
	cm1 = cm2 = as1 = as2 = rs1 = rs2 = 0;
	ClenowMomentum(Days1, "cm1", "as1", "rs1");
	ClenowMomentum(Days2, "cm2", "as2", "rs2");

	am = cm1 * weight1 + cm2 * (1 - weight1);
	VarSet(outAdvancedClenowMomentum, am);
	VarSet(outClenowMomentum1, cm1);
	VarSet(outAdvancedClenowMomentum, am);
	VarSet(outAdvancedClenowMomentum, am);


am = cm1 = cm2 = as1 = as2 = rs1 = rs2 = 0;
d1 = 125;
d2 = 250;
w = 0.5;
ClenowAdvancedMomentum(d1, d2, w, "am", "cm1", "as1", "rs1", "cm2", "as2", "rs2");
printf("am = %g\n", am );
printf("cm1 = %g\n", cm1 );
printf("as1 = %g\n", as1 );
printf("rs1 = %g\n", rs1 );
printf("cm2 = %g\n", cm2 );
printf("as2 = %g\n", as2 );
printf("rs2 = %g\n", rs2 );

If you are a newbie as I am, the following hint might help:
In debugging, add a breakpoint to one of the print lines and then add watches to one or many of the variables am, cm1, etc.
You can then use "Arrays" pane of the debugger to inspect the whole timeline.

I hope, you find this useful.
In following posts I would love to discuss your ideas as well as Andreas suggestions when it comes to position sizing; he published multiple articles about this topic as well, but I have not yet managed to code these ideas in AmiBroker.
Promise: If we could do this together and people will forgive me stupid questions about custom backtesting I will surely raise in the near future, I will disclose everything I develop until we have built all variations of Andreas' ideas into a backtestable and hopefully tradable and profitable system.



Just to simplyfy things:
Both results used for the Clenow Momentum Indicator can be calculated much faster, using builtin AFL functions:
The initially posted code was due to me not having read all posts regarding the topic then. Nevertheless, for those interested, I'll leave it, because watching all these intermediate arrays in debugging might help more people understanding.
Here's the simplified solution, producing same results:

function ClenowMomentum(Days, outClenowMomentum, outAnnualizedSlope, outRsquared)
	x = BarIndex();
	y = Ln(Close);

	k=LinRegSlope(y, Days);
	annSlopePct = 100 * (EXP(250 * k) - 1);
	rSq = (Correlation(y, x, Days))^2;
	cm = annSlopePct * rSq;

	VarSet(outClenowMomentum, cm);
	VarSet(outAnnualizedSlope, annSlopePct);
	VarSet(outRsquared, rSq);

Kudos for this beautifully simplified version to @BlackCat

As I've mentioned before: just started to learn AFL, but willing to share and discuss until we made it.. .

@BlackCat and others have already presented fully working systems in the above thread; anyway, I want to use this one to introduce some alternative ideas, like how to use this with an international stock universe (currently I'm working on FX stuff) or how to test this with variable position sizing approaches and different trading frequencies.
Andreas presented some very interesting ideas, regarding - as he named it - a "More Sophisticated Asset Allocation", which I want to investigate here, too.

Finally, there are already guys - unfortunately not disclosing anything - who combined his approach as low level stock selection, combined with a top level selection like e.g. the GTAA Model by Meb Faber discussed in this thread:

I could buy all this, because it's out there in the market as existing paid add-ons or systems, but I see more thrill in developing all this and sharing it to get a thorough understanding of what I am going to invest into.

I hope, at some point this will seem interesting enough to fellow users to join in.



The 2nd function was incomplete in the original post (I cannot edit anymore, sorry).

Please use this fix (testing code for both functions remains valid):

function ClenowAdvancedMomentum(Days1, Days2, weight1, outAdvancedMomentum, outClenowMomentum1, outAnnualizedSlope1, outRsquared1, outClenowMomentum2, outAnnualizedSlope2, outRsquared2)
	SetBarsRequired(Max(Days1, Days2));

	cm1 = as1 = rs1 = cm2 = as2 = rs2 = 0;

	ClenowMomentum(Days1, "cm1", "as1", "rs1");
	ClenowMomentum(Days2, "cm2", "as2", "rs2");
	am = cm1 * weight1 + cm2 * (1 - weight1);
	VarSet(outAdvancedMomentum, am);
	VarSet(outClenowMomentum1, cm1);
	VarSet(outAnnualizedSlope1, as1);
	VarSet(outRsquared1, rs1);
	VarSet(outClenowMomentum2, cm2); 
	VarSet(outAnnualizedSlope2, as2);
	VarSet(outRsquared2, rs2);

If you want to display this on charts, assuming you have saved the above functions in a file named "" in your MyInclude folder and adjusted Tools-Preferences-AFL-Standard include path to use this folder, you could simply use the following to display SimpleClenowMomentum the way I like to see it in another script, which you place into "Custom" folder, not in the "MyIncludes":

#include <>

d = Param("Days", 90, 2, 1000, 1, 1);

cm = sl = r2 = 0;
ClenowMomentum(d, "cm", "sl", "r2");

// Indicator Chart Line: Clenow Momentum
Plot(cm, StrFormat(_SECTION_NAME() + "(%g)", d), ParamColor("ClenowMomentumColor", colorBrightGreen), styleLine);

// Text only output for annualized Slope and R²
Plot(sl, StrFormat("AnnualizedSlope(%g)", d), ParamColor("ClenowSlopeColor", colorPaleGreen), styleNoRescale+styleNoLine);
Plot(r2, StrFormat("R²(%g)", d), ParamColor("ClenowR2Color", colorPaleTurquoise), styleNoRescale+styleNoLine);



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