John Ehlers used the following EasyLanguage code to calculate the Dominant Cycle in a small sample of data. If you are interested in cycles and noise reduction, definitely check out the books by John Ehlers – “Rocket Science for Traders” or “Cybernetic Analysis for Stocks and Futures.” I am doing some research in this area and wanted to share how I programmed the indicator/function in Python. I refer you to his books or online resources for an explanation of the code. I can tell you it involves an elegantly simplified approach using the Hilbert Transform.
Inputs: Price((H+L)/2);
Vars: Imult(.635),
Qmult (.338),
InPhase(0),
Quadrature(0),
count(0),
Re(0),
Im(0),
DeltaPhase(0),
InstPeriod(0),
Period(0);
If CurrentBar > 8 then begin
Value1 = Price - Price[7];
Inphase = 1.25*(Value1[4] - Imult*Value1[2]) + Imult*InPhase[3];
// print(price," ",price[7]," ",value1," ",inPhase," ",Quadrature," ",self.im[-1]," ",self.re[-1])
// print(d," ",h," ",l," ",c," ",Value1[4]," ",Imult*Value1[2]," ", Imult*InPhase[3]," ",Inphase);
Quadrature = Value1[2] - Qmult*Value1 + Qmult*Quadrature[2];
Re = .2*(InPhase*InPhase[1] + Quadrature*Quadrature[1]) + .8*Re[1];
Im = .2*(InPhase*Quadrature[1] - InPhase[1]*Quadrature) + .8*Im[1];
print(d," ",o," ",h," ",l," ",c," ",value1," ",inPhase," ",Quadrature," ",Re," ",Im);
If Re <> 0 then DeltaPhase = ArcTangent(Im/Re);
{Sum DeltaPhases to reach 360 degrees. The sum is the instantaneous period.}
InstPeriod = 0;
Value4 = 0;
For count = 0 to 50 begin
Value4 = Value4 + DeltaPhase[count];
If Value4 > 360 and InstPeriod = 0 then begin
InstPeriod = count;
end;
end;
{Resolve Instantaneous Period errors and smooth}
If InstPeriod = 0 then InstPeriod = InstPeriod[1];
Period = .25*InstPeriod + .75*Period[1];
Plot1(Period, "DC");
EasyLanguage Code For Calculating Dominant Cycle
In my Python based back tester an indicator of this type is best programmed by using a class. A class is really a simple construct, especially in Python, once you familiarize yourself with the syntax. This indicator requires you to refer to historical values to calculate the next value in the equation: Value1[4], inPhase[1], re[2], etc.,. In EasyLanguage these values are readily accessible as every variable is defined as a BarArray – the complete history of a variable is accessible by using indexing. In my PSB I used lists to store values for those variables most often used such as Open, High, Low, Close. When you need to store the values of let’s say the last five bars its best to just create a list on the fly or build them into a class structure. A Class stores data and data structures and includes the methods (functions) that the data will be pumped into. The follow code describes the class in two sections: 1) data declaration and instantiation and 2) the function to calculate the Dominant Cycle. First off I create the variables that will hold the constant values: imult and qmult. By using the word self I make these variables class members and can access them using “.” notation. I will show you later what this means. I also make the rest of the variables class members, but this time I make them lists and instantiate the first five values to zero. I use list comprehension to create the lists and zero out the first five elements – all in one line of code. This is really just a neat short cut, but can be used for much more powerful applications. Once you create a dominantCycleClass object the object is constructed and all of the data is connected to this particular object. You can create many dominantCycleClass objects and each one would maintain its own data. Remember a class is just a template that is used to create an object.
class dominantCycleClass(object):
def __init__(self):
self.imult = 0.635
self.qmult = 0.338
self.value1 = [0 for i in range(5)]
self.inPhase = [0 for i in range(5)]
self.quadrature = [0 for i in range(5)]
self.re = [0 for i in range(5)]
self.im = [0 for i in range(5)]
self.deltaPhase = [0 for i in range(5)]
self.instPeriod = [0 for i in range(5)]
self.period = [0 for i in range(5)]
Data Portion of Class
The second part of the class template contains the method or function for calculating the Dominant Cycle. Notice how I index into the lists to extract prior values. You will also see the word self. preceding the variable names used in the calculations. Initially I felt like this redundancy hurt the readability of the code and in this case it might. But by using self. I know I am dealing with a class member. This is an example of the ” . ” notation I referred to earlier. Basically this ties the variable to the class.
def calcDomCycle(self,dates,hPrices,lPrices,cPrices,curBar,offset):
tempVal1 = (hPrices[curBar - offset] + lPrices[curBar-offset])/2
tempVal2 = (hPrices[curBar - offset - 7] + lPrices[curBar-offset - 7])/2
self.value1.append(tempVal1 - tempVal2)
self.inPhase.append(1.25*(self.value1[-5] - self.imult*self.value1[-3]) + self.imult*self.inPhase[-3])
self.quadrature.append(self.value1[-3] - self.qmult*self.value1[-1] + self.qmult*self.quadrature[-2])
self.re.append(.2*(self.inPhase[-1]*self.inPhase[-2]+self.quadrature[-1]*self.quadrature[-2])+ 0.8*self.re[-1])
self.im.append(.2*(self.inPhase[-1]*self.quadrature[-2] - self.inPhase[-2]*self.quadrature[-1]) +.8*self.im[-1])
if self.re[-1] != 0.0: self.deltaPhase.append(degrees(atan(self.im[-1]/self.re[-1])))
if len(self.deltaPhase) > 51:
self.instPeriod.append(0)
value4 = 0
for count in range(1,51):
value4 += self.deltaPhase[-count]
if value4 > 360 and self.instPeriod[-1] == 0:
self.instPeriod.append(count)
if self.instPeriod[-1] == 0: self.instPeriod.append(self.instPeriod[-1])
self.period.append(.25*self.instPeriod[-1]+.75*self.period[-1])
return(self.period[-1])
Dominant Cycle Method
Okay we now have the class template to calculate the Dominant Cycle but how do we us it?
#---------------------------------------------------------------------------------
#Instantiate Indicator Classes if you need them
#---------------------------------------------------------------------------------
# rsiStudy = rsiClass()
# stochStudy = stochClass()
domCycle = dominantCycleClass()
#---------------------------------------------------------------------------------
#Call the dominantCycleClass method using " . " notation.
tempVal1 = domCycle.calcDomCycle(myDate,myHigh,myLow,myClose,i,0)
#Notice how I can access class members by using " . " notation as well!
tempVal2 = domCycle.imult
Dominant Cycle Object Creation
Here I assign domCycle the object created by calling the dominantCycleClass constructor. TempVal1 is assigned the Dominant Cycle when the function or method is called using the objects name (domCycle) and the now familiar ” . ” notation. See how you can also access the imult variable using the same notation.
Here is the code in its entirety. I put this in the indicator module of the PSB.
class dominantCycleClass(object):
def __init__(self):
self.imult = 0.635
self.qmult = 0.338
self.value1 = [0 for i in range(5)]
self.inPhase = [0 for i in range(5)]
self.quadrature = [0 for i in range(5)]
self.re = [0 for i in range(5)]
self.im = [0 for i in range(5)]
self.deltaPhase = [0 for i in range(5)]
self.instPeriod = [0 for i in range(5)]
self.period = [0 for i in range(5)]
def calcDomCycle(self,dates,hPrices,lPrices,cPrices,curBar,offset):
tempVal1 = (hPrices[curBar - offset] + lPrices[curBar-offset])/2
tempVal2 = (hPrices[curBar - offset - 7] + lPrices[curBar-offset - 7])/2
self.value1.append(tempVal1 - tempVal2)
self.inPhase.append(1.25*(self.value1[-5] - self.imult*self.value1[-3]) + self.imult*self.inPhase[-3])
self.quadrature.append(self.value1[-3] - self.qmult*self.value1[-1] + self.qmult*self.quadrature[-2])
self.re.append(.2*(self.inPhase[-1]*self.inPhase[-2]+self.quadrature[-1]*self.quadrature[-2])+ 0.8*self.re[-1])
self.im.append(.2*(self.inPhase[-1]*self.quadrature[-2] - self.inPhase[-2]*self.quadrature[-1]) +.8*self.im[-1])
if self.re[-1] != 0.0: self.deltaPhase.append(degrees(atan(self.im[-1]/self.re[-1])))
if len(self.deltaPhase) > 51:
self.instPeriod.append(0)
value4 = 0
for count in range(1,51):
value4 += self.deltaPhase[-count]
if value4 > 360 and self.instPeriod[-1] == 0:
self.instPeriod.append(count)
if self.instPeriod[-1] == 0: self.instPeriod.append(self.instPeriod[-1])
self.period.append(.25*self.instPeriod[-1]+.75*self.period[-1])
return(self.period[-1])
Dominant Cycle Class - Python
Like this:
Like Loading...
Related
Discover more from George Pruitt
Subscribe to get the latest posts sent to your email.
Hi George,
def calcDomCycle(self,dates,hPrices,lPrices,cPrices,curBar,offset):
in this function are we passing individual values for each input or a list, or dataframe ?
Would you be able to show another example of using this ?
Cheers
appreciate the work
Hi Matt,
I programmed this within my TradingSimula18 framework from my latest book – Trend Following Systems – A DIY Project. The class and method uses lists for the inputs. You don’t need to have TradingSimula18 to use the class. All you need to do is get your data into separate lists H, L, C and then loop through the data one bar at a time and call this function to get the output. I kept it simple and stayed away from dataFrames and just went with lists. I will try to create a small script that will pull the data into a list and then call this function to demonstrate how it is used. I will post it here and will send a copy of it via email. Thanks for commenting.