Don’t Be Afraid of Classes in Python (using TS-18 Indicators)

Many of TS-18 Indicators are Class Based and Here is Why!

Many indicators need their prior bar’s value to calculate the current bar’s value.  In Python, external modules or functions that calculate these values have amnesia; they can’t even remember what their prior output was.  In TS-18, all indicators and their associated classes and functions are inside the indicator.py module.  Indicators, such as a simple moving average, do not need to know their prior values so this indicator is function based.  This indicator can do all of its calculations with amnesia.  An indicator such as Wilder’s Directional Movement has multiple output values such as ADX, ADXR, DMIPlus and DMIMinus.  Many of these output values need the prior bar’s value to be calculated properly.  Most of the calculations that need a memory are of the smoothing variety such as an exponential moving average or even a simple moving average.  Since we can’t simply use a function, a class that can store all of the output from all the prior bars is needed.  There’s that word “class” again.  I have already programmed a bunch of indicators in TS-18 and Wilders Directional Movement is one of them.  I am not going to bore you with the actually coding of the indicator, but I am going to show you how to use the indicator in trading system.  I will partially pull the cover off the class by disclosing its instance variables.  These variables contain similar information for each instantiation of the class but can contain different information.  I just said a lot with that last sentence, so let us break it down.  A class is basically a template and when you assign a new variable to a class, you are simply copying the template to that variable name – this is called instantiation.  Take a look at the instance variables to the Wilders Directional Movement class template.

class adxClass(object):
def __init__(self):
self.plusDM = list()
self.minusDM =list()
self.smooth = 0
self.dmi = list()
self.adx = list()
self.adxR = list()
self.DMIPlus = list()
self.DMIMinus = list()
self.seed= 0
self.avgPlusDM = list()
self.avgMinusDM = list()
self.oVolty= list()
Instance variables of the Wilder's DM Class

The word self and dot notation are the syntax that must be used when working with classes.  Self just refers to itself – the template.  All the instance variables except for two are lists.  I made them list in case I need to use any of the variables prior values.  Once I append information to these lists inside the class, I will always have access to each variables’ prior values.  In other words, they have dimension or depth.  Self.smooth is simply the smoothing factor, and it is scalar in type.  Scalar just means it contains a single value, no depth or memory.

In a TS-18 module, you inform TS-18 that you will be using the ADX class by turning in on with True.

 

#region Initiate any lists to hold indicator classes here

for curMarket in range(0,numMarkets):
if useADX == True: adxList.append(adxClass()) # instantiating a list of ADX
if useLaguerre == True: LagRSI.append(laguerreRSIClass())
if useParabolic == True: parabolicList.append(parabolicClass())
if useStochastic == True: stochasticList.append(stochClass())
if useRSI == True: rsiList.append(rsiClass())
if useMACD == True: macdList.append(macdClass())
if useDominantCycle == True: domCycleList.append(dominantCycleClass())
#endregion
Let TS-18 you will be using the ADX Class based Indicator

This code is located near the top of the code listing for the module. If useADX == True, then a list of ADX instantiations is generated by the for-loop.  Why a list?  Remember we need a class or template for each market in the portfolio.  Each element in the list is a variable that refers to a different ADX class.  Remember we leapfrog from one market to another on every day of data.  When we leapfrog, we need to make sure we are looking at the corresponding class that is connected to correct market.  Hence a list.  Take a look at this code and I will go over it afterward.

 

            adxLen = 14
adxList[curMarket].calcADX(myHigh,myLow,myClose,adxLen,curBar,1)
adx = adxList[curMarket].adx
dmiPlus = adxList[curMarket].DMIPlus
dmiMinus = adxList[curMarket].DMIMinus
if len(adx)<2:
break
Accessing the different indicator values of the class

In addition to instance variables, you also have class functions known as methods.  Think of instance variables as nouns and methods as verbs.  Inside the ADX class, I have a function called calcADX.  This is a rather complicated function that calculates the following:  ADX, ADXR, DMIPLUS, and DMIMINUS.  This function/methods needs to be fed the following data to accomplish its task:  high, low and close data for the current market, the length of the indicator, the current bar being evaluated in the portfolio and the offset.  If I want to take action today, I need to know what the ADX values were as of yesterday’s close, so I offset the calculation by one day.  Once the calculation takes place, the instance variables are filled in.  Upon instantiation, the class or template is like a coloring book before it has been touched by a crayon.  Once a class method, that acts upon the instance variables is invoked, the crayons get to work and fill in all the empty spaces.   You gain access to these freshly calculated or colored variables by using dot notation.  The class method is invoked as well with dot notation.

adxList[curMarket].calcADX(myHigh, myLow, myClose, adxLen, curBar,1)

Dot notation refers to the use of a . “ (period or dot) to dig into the class to call the proper method and access the proper instance variable.  The curMarket index refers to the current market in the adxList being evaluated in the back test on the particular day.  Now if you were just testing one market you could do this and not use a list.

myADX = adxClass()


adxList[curMarket].calcADX(myHigh,myLow,myClose,adxLen,curBar,1)
myADX.calcADX(myHigh,myLow,myClose,20,curBar,1)
adx = adxList[curMarket].adx
adx1 = myADX.adx
dmiPlus = adxList[curMarket].DMIPlus
dmiPlus1 = myADX.DMIPlus
dmiMinus = adxList[curMarket].DMIMinus
dmiMinus1 = myADX.DMIMinus
When testing one market only you can bypass the list

Here I assign myADX to the adxClass() template and then use it to call the calcADX method and then use it to extract the instance variables that I need.  In place of 14 as the adxLen in the first method call I use 20 in the second.  Here is the printout when I print out dmiPlus, dmiPlus1, dmiMinus, dimMinus1, adx and adx1.

print("1: ",myDate[D1]," ",dmiPlus[-1]," ",dmiMinus[-1]," ",adx[-1])
print("2: ",myDate[D1]," ",dmiPlus1[-1]," ",dmiMinus1[-1]," ",adx1[-1])


1: 20230522 15.613501032029165 18.564656102938365 12.268908287200347
2: 20230522 16.743185993508668 18.220225160741073 9.197502055582953

1: 20230523 14.67554232281676 19.674357927115043 12.432031623425818
2: 20230523 16.064288436247306 18.983203462673377 9.154049764861858

1: 20230524 13.31919404755149 26.337118114201015 13.888805437719212
2: 20230524 15.052965686929287 23.56514242739481 9.798443826152766
OutPut

Since the instance variables are lists, you must access them using -1 or -2 to get the last and second to last elements.   Take a look at these trade directives to see if they make sense to you.  This strategy buys when the ADX is greater than 25 and DMIPLUS crosses above DMIMINUS.  A $2000 stop is used as a money management stop.  Notice how I test for the two indicators values crossing each other in the Long and Short Entries.

#  Long Entry
if adx[-1] > 25 and dmiPlus[-2] < dmiMinus[-2] and \
dmiPlus[-1] > dmiMinus[-1]:
price = myOpen[curBar]
tradeName = "B:DMI+DMI-"
numShares = posSize
enterLongPosition(price,numShares,tradeName,sysMarkDict)
unPackDict(sysMarkDict)

# Long Exit
if mp == 1 and myLow[curBar] <= entryPrice[-1] - stopAmt and barsSinceEntry > 1:
price = min(myOpen[curBar],entryPrice[-1] - stopAmt)
tradeName = "LxitMM"
numShares = curShares
exitPosition(price, curShares, tradeName, sysMarkDict)
unPackDict(sysMarkDict)
# Short Entry
if adx[-1] > 25 and dmiPlus[-2] > dmiMinus[-2] and \
dmiPlus[-1] < dmiMinus[-1]:
price = myOpen[curBar]
tradeName = "S:DMI-DMI+"
numShares = posSize
enterShortPosition(price, numShares, tradeName, sysMarkDict)
unPackDict(sysMarkDict)
# Short Exit
if mp == -1 and myHigh[curBar] >= entryPrice[-1] + stopAmt and barsSinceEntry > 1:
price = max(myOpen[curBar],entryPrice[-1] + stopAmt)
tradeName = "SxitMM"
numShares = curShares
exitPosition(price, curShares, tradeName, sysMarkDict)
unPackDict(sysMarkDict)
TS-18 Trade Directives

Thi

classic

yes

About This Site

This site is home to George’s Excellent Adventure into TradingSimula_18 and Python.  George grew tired of the old and expensive back testing software so he created his own and now is able to test and develop  Trend Following Systems utilizing EOD data and EOD intra-testing portfolio management.  This software, TradingSimula_18 can be found in his Trend Following Systems: A DIY Project – Batteries Included book – now in its 2nd edition.

May 2024
M T W T F S S
 12345
6789101112
13141516171819
20212223242526
2728293031