Some EasyLanguage Functions Are Really “Classy”

The Series Function is very special

When a function accesses a previously stored value of one of its own variables, it essentially becomes a “series” function. Few programming languages offer this convenience out of the box. The ability to automatically remember a function’s internal state from one bar (or time step) to the next is known as state retention. In languages like Python, achieving this typically requires using a class structure. In Module #2 of my Easing into EasyLanguage Academy, I explained why EasyLanguage isn’t as simple as its name implies—yet this feature shows how its developers aimed to simplify otherwise complex tasks.

If you’ve ever worked with functions with memory that exchange data using a numericRef input, you’ve essentially been using a pseudo-class—a form of object-oriented programming in its own right. EasyLanguage includes a robust object-oriented library (with excellent resources by Sunny Harris and Sam Tennis – buy their book on Amazon), yet you’re confined to its built-in functionality since it doesn’t yet support user-defined class structures. Nonetheless, the series functionality combined with data passing brings you remarkably close to a true object-oriented approach—and the best part is, you might not even realize it.

Example of a Series Function

I was recently working on the Trend Strength indicator/function and have been mulling this post over for some time, so I thought this would be a good time to write about it.  The following indicator to function conversion will create a function of type series (a function with a memory.)  The name series is very appropriate in that this type of function runs along with the time series of the chart.  It must do this so it can reference prior bar values.

You can ensure a function is treated as a series function in two ways:

  1. Using a Prior Value:
    When you reference a previous value within the function, EasyLanguage automatically recognizes the need to remember past data and treats the function as a series function.

  2. Setting the Series Property:
    Alternatively, you can explicitly set the function’s property to “series” via a dialog. This instructs EasyLanguage to handle the function as a series function, ensuring that state is maintained across bars.

    Manually Set the Function to type Series

Importantly, regardless of the function’s name or even if it’s called within control structures (like an if construct), a series function is evaluated on every bar. This guarantees that the historical data is consistently updated and maintained, which is essential for accurate time series analysis.

Converting an indicator to a function

You can find a wide variety of EasyLanguage indicators online, though many are available solely as indicators. This is fine if you’re only interested in plotting the values. However, if you want to incorporate an indicator into a trading strategy, you’ll need to convert it into a function. For calculation-intensive indicators, it’s best to follow a standard prototype: use inputs for interfacing, perform calculations via function calls, and apply the appropriate plotting mechanisms. By adhering to this development protocol, your indicator functions can be reused across different analysis studies, enhancing encapsulation and modularity. Fortunately, converting an indicator’s calculations into a function is a relatively straightforward process. Here is indicator that I found somewhere.

Inputs: 
ShortLength(13), // Shorter EMA length
LongLength(25), // Longer EMA length
SignalSmoothing(7); // Smoothing for signal line
Vars:
DoubleSmoothedPC(0),
DoubleSmoothedAbsPC(0),
TSIValue(0),
SignalLine(0);
// Price Change (PC)
Value1 = Close - Close[1];
// First Smoothing (EMA of PC and |PC|)
Value2 = XAverage(Value1, LongLength);
Value3 = XAverage(AbsValue(Value1), LongLength);
// Second Smoothing (EMA of First Smoothed Values)
DoubleSmoothedPC = XAverage(Value2, ShortLength);
DoubleSmoothedAbsPC = XAverage(Value3, ShortLength);
// Compute TSI
If DoubleSmoothedAbsPC <> 0 Then
TSIValue = 100 * (DoubleSmoothedPC / DoubleSmoothedAbsPC);

// Compute Signal Line
SignalLine = XAverage(TSIValue, SignalSmoothing);
// Plot the TSI and Signal Line
Plot1(TSIValue, "TSI");
Plot2(SignalLine, "Signal");
TSI via Web
Now we can functionize it.

Below is an example of how you might convert an indicator into a function called TrendStrengthIndex. Notice that the first change is to replace any hard-coded numbers in the indicator’s inputs with parameters declared as numericSimple (or numericSeries where appropriate). This allows the function to accept dynamic values when called.  Not to give anything away, but you can also declare variables as numericRef, numericSeries, numericArrayRef, string, stringRef, and stringArrayRef.  Let’s not worry about these types right now.

inputs: 
ShortLength(numericSimple), // Shorter EMA length
LongLength(numericSimple), // Longer EMA length
SignalSmoothing(numericSimple); // Smoothing for signal line
Inputs must be changed to function nomenclature

Below is an example function conversion for the TrendStrengthIndex indicator.  The plot statements have been commented out since—rather than plotting—the function now passes back the calculated value to the calling program.

Vars:
DoubleSmoothedPC(0),
DoubleSmoothedAbsPC(0),
TSIValue(0),
SignalLine(0);
// Price Change (PC)
Value1 = Close - Close[1];
// First Smoothing (EMA of PC and |PC|)
Value2 = XAverage(Value1, LongLength);
Value3 = XAverage(AbsValue(Value1), LongLength);
// Second Smoothing (EMA of First Smoothed Values)
DoubleSmoothedPC = XAverage(Value2, ShortLength);
DoubleSmoothedAbsPC = XAverage(Value3, ShortLength);
// Compute TSI
If DoubleSmoothedAbsPC <> 0 Then
TSIValue = 100 * (DoubleSmoothedPC / DoubleSmoothedAbsPC);

// Compute Signal Line
SignalLine = XAverage(TSIValue, SignalSmoothing);
// Plot the TSI and Signal Line
//Plot1(TSIValue, "TSI"); commented out
//Plot2(SignalLine, "Signal"); commented out
TrendSTrengthIndex = TSIValue;
Functionalize It!

This works great if we just want the TrendStrengthIndex, but this indicator, like many others has a signal line.  The signal line for such indicators is usually a smoothed version of the main calculation.  Now you could do this smoothing outside the function, but wouldn’t it be easier if we did everything inside of the function?

Oh no!  I need to pass more than one value back!

If we just wanted to pass back TSIValue all we need to do is assign the name of the function to this value.

Passing values by reference

We can adjust the function to return multiple values by defining some of the inputs as numericRef. Essentially, when you pass a variable as a numericRef, you’re actually handing over its memory address—okay, let’s get nerdy for a moment! This means that when the function updates the value at that address, the calling routine immediately sees the change, giving the variable a kind of quasi-global behavior. Without numericRef, any modifications made inside the function stay local and never propagate back to the caller.  Not only is the function communicating with the calling strategy or indicator it is also remember its own stuff for future use.  Take a look at this code.

inputs: 
ShortLength(numericSimple), // Shorter EMA length
LongLength(numericSimple), // Longer EMA length
SignalSmoothing(numericSimple), // Smoothing for signal line

TrendStrength.index(numericRef), // Output
TrendStrength.signal(numericRef); //OutPut
Vars:
DoubleSmoothedPC(0),
DoubleSmoothedAbsPC(0),
SignalLine(0);

// Force series FUNCTION BEHAVIOR
Value4 = Value3[1];
// Price Change (PC)
Value1 = Close - Close[1];
// First Smoothing (EMA of PC and |PC|)
Value2 = XAverage(Value1, LongLength);
Value3 = XAverage(AbsValue(Value1), LongLength);

// Second Smoothing (EMA of First Smoothed Values)
DoubleSmoothedPC = XAverage(Value2, ShortLength);
DoubleSmoothedAbsPC = XAverage(Value3, ShortLength);
// Compute TSI
If DoubleSmoothedAbsPC <> 0 Then
TrendStrength.index = 100 * (DoubleSmoothedPC / DoubleSmoothedAbsPC);

// Compute Signal Line
TrendStrength.signal = XAverage(TrendStrength.index, SignalSmoothing);

TrendStrengthIndex = 1;
Is this a function or is it a class?

There is a lot going on here.  Since we are storing our calculations in the two numericRef inputs, TrendStrength.index and TrendStrength.signal the function name can simply be assigned the number 1.  You only need to do this because the function needs to be assigned something, or you will get a syntax error.  Since we are talking objects, I think it would be appropriate to introduce “dot notation.”  When programming with objects you access the class members and methods buy using a dot.  If you have an exponential moving average class in python you would access the variables and functions (methods) in the class like this.

class ExponentialMovingAverage:
# Class-level defaults serve as initial values.
alpha = 0.2 # Default smoothing factor
ema = None # EMA starts as None

def update(self, price):
"""
Update the EMA with a new price.

Parameters:
price (float): The new price to incorporate.

Returns:
float: The updated EMA value.
"""
# If ema is None, this is the first update
if self.ema is None:
self.ema = price
else:
self.ema = self.alpha * price + (1 - self.alpha) * self.ema
return self.ema

# Create an instance of the class.
ema_calculator = ExponentialMovingAverage()

# Dot notation to access the class attribute.
print("Alpha value:", ema_calculator.alpha)

# Dot notation to access the EMA attribute before any updates.
print("Initial EMA (should be None):", ema_calculator.ema)

# Dot notation to call the update method.
ema_value = ema_calculator.update(10)
print("EMA after update with 10:", ema_value)
Using dot notation to extract values from a class

Since you are using EasyLanguage and a series function, you don’t have to deal with something like this.  On the surface this looks a little gross but coming from a programming background this is quite eloquent.  I only show this to demonstrate dot notation.  In an attempt to mimic dot notation in the EasyLanguage function, I simply add a period ” , ” to the input variable names that will return the numbers we need to plot.  Take a look at the nomenclature I am using.

    TrendStrength.index(numericRef), // Output
TrendStrength.signal(numericRef); //OutPut
Function Name +

I am using the function name a ” . ” and an appropriate variable name.  This is not necessary.  Historically, input names that were modified within a function were preceded by the letter “O.”  In this example, Oindex and Osignal.  This represented “output.”  Remember these naming conventions are all up to you.  Here is the new indicator using our EasyLanguage “Classy” function and our pseudo dot notation nomenclature.

//Utilize the TrendStrengthIndex classsy function

inputs: shortLength1(9), longLength1(19), signalSmoothing1(9);
inputs: shortLength2(19), longLength2(39), signalSmoothing2(13);

vars: trendStrength1.index(0), trendStrength1.signal(0);
vars: trendStrength2.index(0), trendStrength2.signal(0);
value1 = TrendStrengthIndex(shortLength1,longLength1,signalSmoothing1,trendStrength1.index,trendStrength1.signal);
value2 = TrendStrengthIndex(shortLength2,longLength2,signalSmoothing2,trendStrength2.index,trendStrength2.signal);


plot1(trendStrength1.index,"TS-Index-1");
plot2(trendStrength1.signal,"TS-Signal-1");

plot3(trendStrength2.index,"TS-Index-2");
plot4(trendStrength2.signal,"TS-Signal-2");
Take a look at how we access the information we need from the function calls.

You might be surprised to learn that you may have been doing object-oriented programming all along without realizing it. Do you prefer the clarity of dot notation for accessing function output, or would you rather stick with a notation that uses a big “O” combined with the input name to represent functions with multiple outputs? Also, notice how each function call behaves like a new instance—the internal values remain discrete, meaning that each call remembers its own state independently.  In other words, each function call remembers its own stuff.

Two distinct function values from the same function – called twice on the same bar.