This post is part of the T4p Series.
In the previous post, I had discussed the engulfing pattern. In this post, we will discuss Moving Averages.
What Is Moving Average
Moving Averages are one of the most widely used indicators. They smooth out price fluctuations by calculating the average price over a specific number of periods, creating a trend-following line that updates as new data becomes available, hence helping traders identify the overall direction of the price movement.
Types Of Moving Averages
- Simple Moving Average (SMA) – Calculates the arithmetic mean of prices over a set period, giving equal weight to all data points
- Exponential Moving Average (EMA) – Gives more weight to recent prices, making it more responsive to current market movements than SMA
- Weighted Moving Average (WMA) – Assigns different weights to each period, typically giving higher importance to more recent data points
- Hull Moving Average (HMA) – Reduces lag while maintaining smoothness by using weighted moving averages of different periods
- Double Exponential Moving Average (DEMA) – Applies exponential smoothing twice to reduce lag and improve responsiveness
- Triple Exponential Moving Average (TEMA) – Further reduces lag by applying exponential smoothing three times while minimizing noise
Usually, traders rely on SMAs and EMAs most of the time.
Usage
Moving averages are primarily used to identify trend direction and generate buy/sell signals when prices cross above or below the average line. They’re important because they filter out market noise and provide clear, objective reference points for making trading decisions and managing risk.
Development
Visualization
I am using the same yfinance
based function to fetch OHLC data, in this case for TSLA
def get_tsla_recent_data(): session = requests.Session() session.headers.update({'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}) tsla = yf.Ticker('TSLA') df = tsla.history(period='4mo', interval='1d') return df
Let’s calculate SMA for 20 and 50.
df = get_tsla_recent_data() df['SMA_short'] = ta.sma(df['Close'], length=20) df['SMA_long'] = ta.sma(df['Close'], length=50)
When you run, it displays like below:
Modifying the visualize_interactive
function:
def visualize_interactive(df, title="TSLA Candlestick Chart"): fig = go.Figure() # Add candlestick chart fig.add_trace(go.Candlestick( x=df.index, open=df['Open'], high=df['High'], low=df['Low'], close=df['Close'], name='TSLA' )) # Add SMA 50 line fig.add_trace(go.Scatter( x=df.index, y=df['SMA_short'], mode='lines', name='SMA Short', line=dict(color='blue', width=2) )) # Add SMA 200 line fig.add_trace(go.Scatter( x=df.index, y=df['SMA_long'], mode='lines', name='SMA Long', line=dict(color='red', width=2) )) fig.update_layout(title=title, xaxis_rangeslider_visible=False) fig.show()
When you run it, it generates a chart like the one below:
This looks good, but unfortunately, it is not crossing over or under in this setup. So, I changed the time duration from 1 day to 4 hours, and now it generates a chart like the one below:
SMA Crossover/Crossunder
SMA crossover is a popular trading strategy that generates buy and sell signals when two simple moving averages of different periods intersect.
- Golden Cross – Short SMA crosses above long SMA = Buy signal
- Death Cross – Short SMA crosses below long SMA = Sell signal
Let’s write a function that will generate these crossover signals
def generate_ma_crossover_signals(df, short_ma_col='SMA_short', long_ma_col='SMA_long'): df = df.copy() # Initialize signal column df['Signal'] = 0 # Generate crossover signals # Buy signal: Short MA crosses above Long MA (Golden Cross) df['Signal'] = np.where( (df[short_ma_col] > df[long_ma_col]) & (df[short_ma_col].shift(1) <= df[long_ma_col].shift(1)), 1, df['Signal'] ) # Sell signal: Short MA crosses below Long MA (Death Cross) df['Signal'] = np.where( (df[short_ma_col] < df[long_ma_col]) & (df[short_ma_col].shift(1) >= df[long_ma_col].shift(1)), -1, df['Signal'] ) # Add signal markers for plotting df['Buy_Signal'] = np.where(df['Signal'] == 1, df['Close'], np.nan) df['Sell_Signal'] = np.where(df['Signal'] == -1, df['Close'], np.nan) return df
And now, when you call it:
df_with_signals = generate_ma_crossover_signals(df) print("Buy Signals:") print(df_with_signals[df_with_signals['Signal'] == 1][['Close', 'SMA_short', 'SMA_long']]) print("\nSell Signals:") print(df_with_signals[df_with_signals['Signal'] == -1][['Close', 'SMA_short', 'SMA_long']])
And it produces the output
And this is exactly what is happening.
But it is not so good. How cool would it be to add a visual hint about it? The following function does this:
def visualize_interactive_with_signals(df, title="TSLA with MA Crossover Signals"): fig = go.Figure() # Candlestick chart fig.add_trace(go.Candlestick( x=df.index, open=df['Open'], high=df['High'], low=df['Low'], close=df['Close'], name='TSLA' )) # Moving averages fig.add_trace(go.Scatter(x=df.index, y=df['SMA_short'], mode='lines', name='SMA Short', line=dict(color='blue', width=2))) fig.add_trace(go.Scatter(x=df.index, y=df['SMA_long'], mode='lines', name='SMA Long', line=dict(color='red', width=2))) # Buy signals with text buy_signals = df[df['Signal'] == 1] if not buy_signals.empty: fig.add_trace(go.Scatter( x=buy_signals.index, y=buy_signals['Close'], mode='markers+text', name='Buy Signal', marker=dict(symbol='triangle-up', size=15, color='green'), text='BUY', textposition='top center', textfont=dict(color='green', size=12, family='Arial Black') )) # Sell signals with text sell_signals = df[df['Signal'] == -1] if not sell_signals.empty: fig.add_trace(go.Scatter( x=sell_signals.index, y=sell_signals['Close'], mode='markers+text', name='Sell Signal', marker=dict(symbol='triangle-down', size=15, color='red'), text='SELL', textposition='bottom center', textfont=dict(color='red', size=12, family='Arial Black') )) fig.update_layout(title=title, xaxis_rangeslider_visible=False) fig.show()
And it produces the following chart, with signals. Cool, no?
Conclusion
As you see, generating signals based on Moving Averages in Python is easy. I used SMA for the sake of the post, but you can do something similar with EMAs as well. As always, the code is available on GitHub
Looking to create something similar or even more exciting? Schedule a meeting or email me at kadnan @ gmail.com.
Love What You’re Learning Here?
If my posts have sparked ideas or saved you time, consider supporting my journey of learning and sharing. Even a small contribution helps me keep this blog alive and thriving.