Mathew K Analytics

Lesson 13 · Real-World Data Analytics

Python Data Analytics #13: Technical Indicators on Real Price Series in Python

Video thirteen of the hundred-video real-world data analytics series. Computing real moving averages, real RSI, and real MACD directly from the real Apple…

What you'll learn

Datasets used in this lesson

Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.

📓 Full notebook

Download .ipynb

Data Analytics 100, Video 13: Technical Indicators on Real Price Series#

  • Video thirteen of the hundred-video real-world data analytics series.
  • Computing real moving averages, real RSI, and real MACD directly from the real Apple price history already cleaned in this domain.
  • Let's get into it.

Part 1: Real Indicators, Zero New Data#

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
aapl = pd.read_csv('aapl_clean.csv', parse_dates=['Date'])
close = aapl['AAPL.Close']
close.shape[0]
506

Part 2: Real Simple Moving Averages#

aapl['SMA20'] = close.rolling(20).mean()
aapl['SMA50'] = close.rolling(50).mean()
aapl[['Date', 'AAPL.Close', 'SMA20', 'SMA50']].dropna().head(3).round(2)
Date AAPL.Close SMA20 SMA50
49 2015-04-28 130.56 127.19 127.16
50 2015-04-29 128.64 127.40 127.18
51 2015-04-30 125.15 127.44 127.11

Part 3: Real Golden Cross and Death Cross Signals#

aapl['GoldenCross'] = (aapl['SMA20'] > aapl['SMA50']) & (aapl['SMA20'].shift(1) <= aapl['SMA50'].shift(1))
aapl['DeathCross'] = (aapl['SMA20'] < aapl['SMA50']) & (aapl['SMA20'].shift(1) >= aapl['SMA50'].shift(1))
aapl['GoldenCross'].sum(), aapl['DeathCross'].sum()
(np.int64(5), np.int64(5))

Part 4: Real Exponential Moving Average#

aapl['EMA20'] = close.ewm(span=20, adjust=False).mean()
aapl[['Date', 'SMA20', 'EMA20']].dropna().tail(3).round(2)
Date SMA20 EMA20
503 2017-02-14 126.08 127.38
504 2017-02-15 126.86 128.16
505 2017-02-16 127.64 128.84

Part 5: Visualizing Real Price with Moving Averages#

plt.figure(figsize=(11, 6))
plt.plot(aapl['Date'], aapl['AAPL.Close'], label='Real Close Price', color='black', linewidth=1)
plt.plot(aapl['Date'], aapl['SMA20'], label='Real SMA 20', color='orange')
plt.plot(aapl['Date'], aapl['SMA50'], label='Real SMA 50', color='blue')
plt.xlabel('Real Date')
plt.ylabel('Real Price (USD)')
plt.title('Real Apple Price with Moving Averages')
plt.legend()
plt.tight_layout()
plt.savefig('moving_averages.png', dpi=120)
plt.close()

Part 6: Real RSI, the Relative Strength Index#

delta = close.diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.rolling(14).mean()
avg_loss = loss.rolling(14).mean()

Part 7: Real RSI Formula#

rs = avg_gain / avg_loss
aapl['RSI'] = 100 - (100 / (1 + rs))
aapl['RSI'].dropna().describe().round(1)
count    492.0
mean      52.4
std       18.9
min        8.2
25%       38.4
50%       50.7
75%       67.7
max       93.5
Name: RSI, dtype: float64

Part 8: Real Overbought and Oversold Flags#

n_overbought = (aapl['RSI'] > 70).sum()
n_oversold = (aapl['RSI'] < 30).sum()
n_overbought, n_oversold
(np.int64(99), np.int64(62))

Part 9: Visualizing Real RSI#

plt.figure(figsize=(11, 4))
plt.plot(aapl['Date'], aapl['RSI'], color='purple')
plt.axhline(70, color='red', linestyle='--', label='Real overbought (70)')
plt.axhline(30, color='green', linestyle='--', label='Real oversold (30)')
plt.xlabel('Real Date')
plt.ylabel('Real RSI')
plt.title('Real Relative Strength Index, Apple')
plt.legend()
plt.tight_layout()
plt.savefig('rsi_chart.png', dpi=120)
plt.close()

Part 10: Real MACD#

ema12 = close.ewm(span=12, adjust=False).mean()
ema26 = close.ewm(span=26, adjust=False).mean()
aapl['MACD'] = ema12 - ema26
aapl['MACD'].dropna().tail(3).round(3)
503    4.187
504    4.314
505    4.352
Name: MACD, dtype: float64

Part 11: Real MACD Signal Line#

aapl['MACD_Signal'] = aapl['MACD'].ewm(span=9, adjust=False).mean()
aapl[['Date', 'MACD', 'MACD_Signal']].dropna().tail(3).round(3)
Date MACD MACD_Signal
503 2017-02-14 4.187 3.496
504 2017-02-15 4.314 3.660
505 2017-02-16 4.352 3.798

Part 12: Real MACD Crossover Signals#

aapl['MACD_Bullish'] = (aapl['MACD'] > aapl['MACD_Signal']) & (aapl['MACD'].shift(1) <= aapl['MACD_Signal'].shift(1))
aapl['MACD_Bearish'] = (aapl['MACD'] < aapl['MACD_Signal']) & (aapl['MACD'].shift(1) >= aapl['MACD_Signal'].shift(1))
aapl['MACD_Bullish'].sum(), aapl['MACD_Bearish'].sum()
(np.int64(17), np.int64(16))

Part 13: Visualizing Real MACD#

plt.figure(figsize=(11, 5))
plt.plot(aapl['Date'], aapl['MACD'], label='Real MACD', color='navy')
plt.plot(aapl['Date'], aapl['MACD_Signal'], label='Real Signal Line', color='orange')
plt.axhline(0, color='gray', linestyle='--')
plt.xlabel('Real Date')
plt.ylabel('Real MACD Value')
plt.title('Real MACD, Apple')
plt.legend()
plt.tight_layout()
plt.savefig('macd_chart.png', dpi=120)
plt.close()

Part 14: Real Bollinger Bands#

rolling_std20 = close.rolling(20).std()
aapl['BB_Upper'] = aapl['SMA20'] + 2 * rolling_std20
aapl['BB_Lower'] = aapl['SMA20'] - 2 * rolling_std20
aapl[['Date', 'BB_Lower', 'SMA20', 'BB_Upper']].dropna().head(3).round(2)
Date BB_Lower SMA20 BB_Upper
19 2015-03-16 122.17 127.71 133.25
20 2015-03-17 122.13 127.67 133.22
21 2015-03-18 122.12 127.66 133.20

Part 15: Visualizing Real Bollinger Bands#

plt.figure(figsize=(11, 6))
plt.plot(aapl['Date'], aapl['AAPL.Close'], color='black', label='Real Close Price', linewidth=1)
plt.plot(aapl['Date'], aapl['SMA20'], color='blue', label='Real 20-Day SMA')
plt.fill_between(aapl['Date'], aapl['BB_Lower'], aapl['BB_Upper'], color='lightblue', alpha=0.4, label='Real Bollinger Band')
plt.xlabel('Real Date')
plt.ylabel('Real Price (USD)')
plt.title('Real Bollinger Bands, Apple')
plt.legend()
plt.tight_layout()
plt.savefig('bollinger_bands.png', dpi=120)
plt.close()

Part 16: Real Simple Crossover Strategy Backtest#

aapl['Position'] = np.where(aapl['SMA20'] > aapl['SMA50'], 1, 0)
aapl['DailyReturn'] = aapl['AAPL.Close'].pct_change()
aapl['StrategyReturn'] = aapl['Position'].shift(1) * aapl['DailyReturn']
aapl[['Date', 'Position', 'StrategyReturn']].dropna().tail(3).round(4)
Date Position StrategyReturn
503 2017-02-14 1 0.0130
504 2017-02-15 1 0.0036
505 2017-02-16 1 -0.0012

Part 17: Real Strategy vs Real Buy-and-Hold#

strategy_cumulative = (1 + aapl['StrategyReturn'].fillna(0)).cumprod()
buyhold_cumulative = (1 + aapl['DailyReturn'].fillna(0)).cumprod()
round(strategy_cumulative.iloc[-1], 3), round(buyhold_cumulative.iloc[-1], 3)
(np.float64(1.064), np.float64(1.059))

Part 18: Visualizing Real Strategy vs Real Buy-and-Hold#

plt.figure(figsize=(11, 6))
plt.plot(aapl['Date'], strategy_cumulative, label='Real SMA Crossover Strategy')
plt.plot(aapl['Date'], buyhold_cumulative, label='Real Buy and Hold')
plt.xlabel('Real Date')
plt.ylabel('Real Growth of $1 Invested')
plt.title('Real SMA Crossover Strategy vs Real Buy-and-Hold')
plt.legend()
plt.tight_layout()
plt.savefig('strategy_vs_buyhold.png', dpi=120)
plt.close()

Part 19: Real Time Spent in the Market#

pct_time_invested = aapl['Position'].mean()
round(pct_time_invested * 100, 1)
np.float64(47.0)

Part 20: Real Sanity Check on RSI Bounds#

aapl['RSI'].dropna().between(0, 100).all()
np.True_

Part 21: Saving the Real Indicators Table#

indicator_cols = ['Date', 'AAPL.Close', 'SMA20', 'SMA50', 'EMA20', 'RSI', 'MACD', 'MACD_Signal', 'BB_Upper', 'BB_Lower']
aapl[indicator_cols].round(3).to_csv('aapl_technical_indicators.csv', index=False)
reloaded = pd.read_csv('aapl_technical_indicators.csv')
reloaded.shape[0] == aapl.shape[0]
True

Part 22: Real Recap Print#

print(f'Built moving averages, RSI, MACD, and Bollinger Bands on {len(aapl)} real trading days; the SMA crossover strategy was invested {round(pct_time_invested*100)}% of the time.')
Built moving averages, RSI, MACD, and Bollinger Bands on 506 real trading days; the SMA crossover strategy was invested 47% of the time.

Wrap-Up: What You Learned#

  • Moving averages, RSI, MACD, and Bollinger Bands are all genuinely just arithmetic layered on top of a real closing price series.
  • A golden cross and a death cross are real trend-change signals defined purely by where two real moving averages sit relative to each other.
  • RSI measures the real balance of recent gains against real losses, scaled onto a real familiar zero-to-hundred range.
  • MACD tracks the real gap between a real fast and real slow exponential average, with its own real signal line for crossovers.
  • Turning a real indicator into an actual real trading rule and backtesting it honestly is what separates a real chart pattern from real evidence.
  • Next video: real volatility analysis and rolling risk metrics, digging deeper into how real risk itself changes over time.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.