Petra on Programming: The Correlation Cycle Indicator

The previous article dealt with indicators based on correlation with a trend line. This time we’ll look into another correlation-based indicator by John Ehlers. The new Correlation Cycle indicator (CCY) measures the price curve correlation with a sine wave. This works surprisingly well – not for generating trade signals, but for a different purpose.

Ehlers published the indicator together with TradeStation code in the recent S&C magazine. Since the C language supports function pointers, we can code it in a shorter and more elegant way:

var correlY(var Phase); // function pointer
var cosFunc(var Phase) { return cos(2*PI*Phase); }
var sinFunc(var Phase) { return -sin(2*PI*Phase); }

var correl(vars Data, int Length, function Func)
{
   correlY = Func; 
   var Sx = 0, Sy = 0, Sxx = 0, Sxy = 0, Syy = 0;
   int count;
   for(count = 0; count < Length; count++) {
      var X = Data[count];
      var Y = correlY((var)count/Length);
      Sx += X; Sy += Y;
      Sxx += X*X; Sxy += X*Y; Syy += Y*Y;
   }
   if(Length*Sxx-Sx*Sx > 0 && Length*Syy-Sy*Sy > 0)
      return (Length*Sxy-Sx*Sy)/sqrt((Length*Sxx-Sx*Sx)*(Length*Syy-Sy*Sy));
   else return 0;
}

var CCY(vars Data, int Length) { return correl(Data,Length,cosFunc); }
var CCYROC(vars Data, int Length) { return correl(Data,Length,sinFunc); }

The correl function measures the correlation of the Data series with an arbitrary curve given by the Func function. This allows us to create all sorts of correlation indicators by just using a different Func. For example, it reduces Ehlers’ Correlation Trend Indicator from the previous article to 2 lines:

var trendFunc(var Phase) { return -Phase; }

var CTI(vars Data,int Length) { return correl(Data,Length,trendFunc); }
 

The empty correlY function pointer in the code above serves as a template for Func, and is used for calling it inside the correl function. For the CTI it’s simply a rising slope (negative because series are in reverse order), for the CCY it’s the standard cosine function.

At first let’s see how the CCY indicator behaves when applied to a sine wave. We’re using Zorro’s wave generator to produce a sine chirp with a rising cycle length from 15 up to 30 bars, which is 25% below and 50% above the used CCY period of 20 bars. The code:

function run()
{
MaxBars = 300;
LookBack = 40;
asset(""); // dummy asset
ColorUp = ColorDn = 0; // don't plot a price curve

vars Chirp = series(genSine(15,30));
plot("Chirp",2*Chirp[0]-1,LINE,BLUE);
plot("CCY",CCY(Chirp,20),LINE,RED);
}

And the result:

This confirms Ehlers’ stress test. A shorter period results in a phase lag, a longer period in a phase lead. We’re now going to apply the indicator to real-world price curves. This code displays the CCY and its rate of change (CCYROC) in a SPY chart:

function run()
{
BarPeriod = 1440; LookBack = 40; StartDate = 20190101; assetAdd("SPY","STOOQ:SPY.US"); // load price history from Stooq asset("SPY"); vars Prices = series(priceClose()); plot("CCY(14)",CCY(Prices,14),NEW|LINE,RED); plot("ROC(14)",CCYROC(Prices,14),LINE,GREEN); }

What’s the use of the Correlation Cycle indicator in a trading system? The chart might hint that its peaks or valleys could be used for trade signals, but you can save the time of testing it: I did already. The CCY is no good for trade signals. But Ehlers had another idea. The phase angle of the CCY and CCYROC reflects the market state. It returns 1 for a rising trend, -1 for a falling trend, and 0 for cycle regime. Here’s the code of Ehlers CCY market state indicator:

var CCYState(vars Data,int Length,var Threshold)
{
vars Angles = series(0,2);
var Real = correl(Data,Length,cosFunc);
var Imag = correl(Data,Length,sinFunc);
// compute the angle as an arctangent function and resolve ambiguity
if(Imag != 0) Angles[0] = 90 + 180/PI*atan(Real/Imag);
if(Imag > 0) Angles[0] -= 180;
// do not allow the rate change of angle to go negative
if(Angles[1]-Angles[0] < 270 && Angles[0] < Angles[1])
Angles[0] = Angles[1];
//return Angles[0];
// compute market state
if(abs(Angles[0]-Angles[1]) < Threshold)
return ifelse(Angles[0] < 0,-1,1);
else return 0;
}

Applied to SPY:

At first glance, trends and cycles seem to be rather well and timely detected. But how useful is the indicator in a real trading system?

For finding out, we’ll compare the performance with and without market state detection of a simple trend follower, as in the first Zorro workshop. It uses a lowpass filter for detecting trend reversals. The only parameter is the cutoff period of the low pass filter. This parameter is walk forward optimized, so the system does depend on any choosen parameter value. The trend follower without market state detection:

function run() 
{
   set(PARAMETERS);
   BarPeriod = 1440;
   LookBack = 40;
   NumYears = 8;

   assetAdd("SPY","STOOQ:SPY.US"); // load price history from Stooq
   asset("SPY");
   
   NumWFOCycles = 4;
   int Cutoff = optimize(10,5,30,5);

   vars Prices = series(priceClose());
   vars Signals = series(LowPass(Prices,Cutoff));
   if(valley(Signals))
      enterLong();
   else if(peak(Signals))
      enterShort();
}

The system enters a long position on any valley of the lowpass filtered price curve, and a short position on any peak. The resulting equity curve:

We can see that the simple SPY trend follower is not very good. Yes, it’s profitable, but the main profit came from some lucky trades at the corona drop. In the years before the system had long flat periods. Let’s see if the CCYState indicator can help. Its two parameters, period and threshold, are also walk forward optimized. The new script:

function run() 
{
   set(PARAMETERS);
   BarPeriod = 1440;
   LookBack = 40;
   NumYears = 8;

   assetAdd("SPY","STOOQ:SPY.US"); // load price history from Stooq
   asset("SPY");
   
   NumWFOCycles = 4;
   int Cutoff = optimize(10,5,30,5);
   int Period = optimize(14,10,25,1);
   var Threshold = optimize(9,5,15,1);

   vars Prices = series(priceClose());
   var State = CCYState(Prices,Period,Threshold);
   plot("State",State*0.9,NEW|LINE,BLUE);
   vars Signals = series(LowPass(Prices,Cutoff));
   
   if(State != 0) {
      if(valley(Signals))
         enterLong();
      else if(peak(Signals))
         enterShort();
   }
   else {
      exitLong(); 
      exitShort();
   }
}

The new system trades only when the market state is 1 or -1, indicating trend regime. It goes out of the market when the market state is 0. We can see that this improves the equity curve remarkably:

I think most people would prefer this system to the previous one, even though it stayed out of the market at the corona drop. Ehlers’ market state indicator did a good job.

Reference

John Ehlers, Correlation Cycle Indicator, Stocks&Commodities 6/2020

The indicators and trade systems are available in the Scripts 2020 repository.

6 thoughts on “Petra on Programming: The Correlation Cycle Indicator”

  1. it fits well into certain market conditions in sinusoidal phase.But other times it fells apart.

  2. Hey Petra,

    What is the ROC function used? It looks bounded vs the formula Tradingview uses:
    ROC = [(CurrentClose – Close n periods ago) / (Close n periods ago)] X 100

    thanks

  3. Hi Petra,

    I wonder how stable a system based on this could be when adding an additional signal-/entry- and position management system for the cyclic phases as well….

    cheers

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.