DEV Community

Suleyman Sade
Suleyman Sade

Posted on • Originally published at Medium

From SMA to TEMA: Coding Technical Indicators in Python — Building stocksimpy 2

Graph of different indicators

Most of the stock back-testing tools and libraries I've seen are either too complex for beginners or have a steep learning curve. So I decided to build my own Python library from scratch called stocksimpy which aims to be a light-weight and well-documented alternative to what is out there.

One of my primary goals with this series is to learn how Python libraries work, as well as to improve my skills in Pandas and NumPy. And create something that could grow into a real tool for others to use.

In the process, I ended up implementing over 10 indicators from scratch.

Here’s how I approached, what I learned, and how you can do the same:

Why Build Indicators From Scratch?

This is a valid question. I also asked myself this when I reached about 300 lines of code. But then I realised that when I was coding these indicators, I was learning how they work. This hit me when suddenly the equation for TEMA calculation appeared in my eyes:

(3 * EMA_1) — (3*EMA_2) + EMA_3

This was enough to prove that I was learning something, and I was also getting better at using Pandas.

  • This was also my Python library that I built from zero, so I wanted to have total control over what to include and how to organize them.
  • Also, something about Python libraries is that you want to keep dependencies to a minimum, and I already had to use NumPy and Pandas , so adding another packed dependency might have been too much — which I tried to avoid.

Deep Dive Into Common Indicators

Overview of indicators implemented so far:

  • Moving Averages: SMA, EMA, DEMA, TEMA, HMA
  • Momentum Indicators: RSI, MACD, Wilder’s MACD, HMA-MACD, TEMA-MACD
  • Smoothing: Wilder’s Smoothing

Now let's break down each one of them as well as their implementations:

Simple Moving Average (SMA)

SMA is just the average of a fixed number of consecutive values in a series. Let’s break it down:

data_series = [1, 3, 4, 6, 8]

window = 3

Now, calculate the average of each group of consecutive numbers:

The first two numbers don’t have enough data before them to form a full window, so we assign NaN for them.

Next values are:

  • (1 + 3 + 4)/3 = 2.67
  • (3 + 4 + 6)/3 = 4.33
  • (4 + 6 + 8)/3 = 6.0

So the result should look like this:

[Nan, Nan, 2.67, 4.33, 6.0]

This could be implemented easily in Python:

def calculate_sma(data_series: pd.Series, window: int):
  return data_series.rolling(window).mean()
Enter fullscreen mode Exit fullscreen mode

In this code, we use .rolling(window) to create a moving window of specified size, and get the average of each using mean().

Weighted Moving Average (WMA)

While SMA gives equal weight to all the values in a given window, we sometimes want to give more weight to recent values to make it more responsive to recent price changes. This is when WMA is used:

The formula is linear:

WMA(n) = (V₀ × n + V₁ × (n−1) + V₂ × (n−2) + … + Vₙ₋₁ × 1) / (n + (n−1) + … + 1)

Where:

  • V₀ is the most recent value
  • n is the window size
  • The denominator is the sum of the weights: n(n + 1)/2

Exponential Moving Average (EMA):

WMA helps with giving some weight, but sometimes you want to be even more responsive to recent changes. This is where we use EMA, which weights exponentially instead of linearly, and also forms the foundation for indicators like MACD.

Calculating EMA is a bit more complicated than the previous two.

To calculate the current EMA value, you need the EMA of the previous day, so it requires a recursive pattern. Here is the equation:

equation for EMA
Source: How Is the Exponential Moving Average (EMA) Formula Calculated?

Even though the calculation seems complicated, the code implementation is pretty easy:

def calculate_ema(data_series: pd.Series, window: int) -> pd.Series:
  return data_series.ewm(span=window, adjust=False, min_periods=window).mean()
Enter fullscreen mode Exit fullscreen mode

Here, .ewm() applies the exponential weighting and span=window ensures that it uses the k equation described above. adjust=false makes sure that it uses recursion. And the final .mean() calculates the EMA.

Double Exponential Moving Average (DEMA)

DEMA is designed to reduce the lag that is found in EMA by combining two EMAs into one formula.

Formula is as follows:

DEMA = 2 × EMA1 — EMA2

  • EMA1: standard EMA
  • EMA2: EMA of EMA1

Meaning you first find the standard EMA and then find the EMA of EMA, and finish by combining these two EMAs into one.

Why does DEMA reduce lag?

In standard EMA or SMA, sudden changes in value take time to recover — since we are still using the previous values to get the EMA/SMA. For example, in [100, 101, 102, 120, 121, 122] there is a sudden increase from 102 to 120, but when calculating EMA, the first values that are close to 100s are going to bring the average down from 120 to 110ish, which is called lag.

What DEMA does is that it calculates the EMA of EMA so that it lags even further and then subtracts it from the standard EMA times 2, essentially pulling the result forward.

Here is a Python implementation of DEMA using the previous calculate_ema() function:

def calculate_dema(data_series: pd.Series, window: int) -> pd.Series:
  ema1 = calculate_ema(data_series, window)
  ema2 = calculate_ema(ema1, window)

  return (2 * ema1) - ema2
Enter fullscreen mode Exit fullscreen mode

We calculate the ema1 by getting the EMA of data_series and then ema2 by calculating the EMA of ema1. Then we do the calculation and return it — pretty simple.

Triple Exponential Moving Average (TEMA):

TEMA takes the concept behind DEMA one step further by combining 3 EMAs into one, smoother, and even more responsive indicator.

The formula looks slightly complex, but it follows a similar idea:

TEMA = (3 × EMA_1) — (3 × EMA_2) + EMA_3

  • EMA_1: Standard EMA
  • EMA_2: EMA of EMA_1
  • EMA_3: EMA of EMA_3

So, how does this help?

Each layer adds more smoothness — but also more lag.

TEMA corrects for this by amplifying the most recent EMA (3 × EMA_1), which results in 3×lag, subtracting out the additional lag (3 × EMA_2), which is 3xlag - 6xlag = -3lag. Then the final EMA_3 is used to avoid overcompensation: -3lag + 3lag = 0lag — which means no lag.

Here is my Python implementation in stocksimpy :

def calculate_tema(data_series: pd.Series, window: int) -> pd.Series:  
  ema1 = calculate_ema(data_series, window)
  ema2 = calculate_ema(ema1, window)
  ema3 = calculate_ema(ema2, window)

  return (3*ema1) - (3*ema2) + ema3
Enter fullscreen mode Exit fullscreen mode

I think the code is pretty straightforward. It uses calculate_ema() to calculate ema1 , ema2 and ema3 . Then the equation is implemented.

What’s Next?

These moving averages (MA) form the foundation of many powerful technical indicators used in trading and analysis. By building them from scratch, you not only gain a deeper understanding of how they work but also how to improve your skills with Pandas and NumPy .

In the next article in this series, I’ll dive into momentum indicators like RSI, MACD, and some custom variations — showing you how to implement these step-by-step.


I write about learning AI by building real tools, not just tutorials.


Here is the GitHub link to stocksimpy which contains all the functions used in this article, and most of the equations:

https://github.com/SuleymanSade/StockSimPy/tree/dev?source=post_page-----16e684091789---------------------------------------

Top comments (0)