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…
- CourseReal-World Data Analytics
- Lesson13 of 100
- Video23 min
- FormatJupyter notebook · 22 code cells
- Data1 dataset
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.
- aapl_clean.csv58.9 KB
📓 Full notebook
Download .ipynbData 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]
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)
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()
Part 4: Real Exponential Moving Average#
aapl['EMA20'] = close.ewm(span=20, adjust=False).mean()
aapl[['Date', 'SMA20', 'EMA20']].dropna().tail(3).round(2)
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)
Part 8: Real Overbought and Oversold Flags#
n_overbought = (aapl['RSI'] > 70).sum()
n_oversold = (aapl['RSI'] < 30).sum()
n_overbought, n_oversold
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)
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)
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()
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)
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)
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)
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)
Part 20: Real Sanity Check on RSI Bounds#
aapl['RSI'].dropna().between(0, 100).all()
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]
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.')
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.



