Python for Algorithmic Trading: Historical Data Ingestion & Technical Momentum Strategies
What You Will Master in This Tutorial
- Fetch clean historical stock data for equities and ETFs using modern Python APIs
- Calculate vectorized indicators: Exponential Moving Averages (EMA 20/50), MACD, and RSI
- Generate deterministic buy and sell signals without lookahead bias
- Compute key risk metrics: Maximum Drawdown, Sharpe Ratio, and CAGR
import pandas as pd
import numpy as np
def calculate_momentum_strategy(df: pd.DataFrame, fast: int = 20, slow: int = 50) -> pd.DataFrame:
"""
Computes EMA crossover signals and backtests portfolio equity curve.
"""
df = df.copy()
df['EMA_Fast'] = df['Close'].ewm(span=fast, adjust=False).mean()
df['EMA_Slow'] = df['Close'].ewm(span=slow, adjust=False).mean()
# Generate position signal (1 for Long, 0 for Cash)
df['Signal'] = np.where(df['EMA_Fast'] > df['EMA_Slow'], 1, 0)
df['Position'] = df['Signal'].shift(1) # Avoid lookahead bias
# Daily logarithmic returns
df['Market_Return'] = np.log(df['Close'] / df['Close'].shift(1))
df['Strategy_Return'] = df['Position'] * df['Market_Return']
df['Cumulative_Market'] = df['Market_Return'].cumsum().apply(np.exp)
df['Cumulative_Strategy'] = df['Strategy_Return'].cumsum().apply(np.exp)
return df
Frequently Asked Questions
Going deeper: momentum backtesting in practice
The strategy in this tutorial β an EMA 20/50 crossover with positions shifted by one bar β is deliberately simple, and that is the point: it isolates the mechanics that matter in any backtest. In practice, the same workflow extends to cleaning and aligning historical data before a single signal is computed, validating parameters on periods the strategy was not tuned on, and modeling the frictions the code leaves out, such as commissions, spreads and slippage. The risk metrics introduced here β maximum drawdown, Sharpe ratio, and CAGR β are how you judge whether a curve is robust or just lucky. Remember that every number a backtest produces is simulated historical performance: it describes what would have happened in the past, not what will happen live, and it never guarantees future results.
Common mistakes
- Lookahead bias. Computing a signal from the same bar's close and trading on it in that same bar leaks future information; the tutorial's one-bar shift exists precisely to prevent this.
- Ignoring transaction costs. A crossover system trades frequently, and commissions plus slippage can quietly turn a profitable-looking equity curve into a losing one.
- Overfitting the parameters. EMA spans tuned until one historical period looks perfect usually fail on new data; test the strategy on periods you did not optimize against.
- Survivorship and data bias. Backtesting only on stocks that exist today, or on unadjusted prices, inflates results because delisted failures and splits are mishandled.
Frequently asked questions
Why does the code shift the signal by one bar?
So that each day's position is decided only from data that was fully known the day before. Without the shift, the backtest would be trading on information from the future.
If a backtest looks profitable, will the strategy make money live?
Not necessarily. Backtests are simulated historical performance without real execution costs, slippage, or changing market regimes, and they never guarantee future results.
Which risk metric should I watch first?
Maximum drawdown. Sharpe ratio and CAGR describe return quality, but drawdown tells you the worst peak-to-trough loss you would have had to sit through to earn them.
Should I re-optimize the EMA periods for every stock?
Be careful. Tuning the fast and slow spans separately for each symbol is one of the fastest routes to overfitting: the parameters memorize that stock's past instead of capturing a repeatable effect. A more robust approach is to pick one reasonable parameter set, validate it across many symbols and time periods with walk-forward testing, and accept the strategies that survive broadly rather than the ones that shine on a single chart.