Quant Trading in Python — Part 1 of 6

Setup — Your Quant Lab in 30 Minutes

Before you write your first strategy, you need the right tools. We'll install Python, set up your environment, pull real market data, and structure your project like a professional quant.

Python 3.11+ Market Data Project Structure First Chart
Quant Trading in Python1/6
Why PythonEnvironment SetupEssential LibrariesFirst Data PullData ExplorationProject Structure
Why Python for Quant Trading

Why Python Dominates Quant Finance

Python is the lingua franca of quantitative finance. From two-person prop shops to Goldman Sachs, Python is the tool of choice for backtesting, signal generation, risk management, and increasingly, live execution. Here's why:

Rapid Prototyping

Test a new strategy idea in 50 lines. No compilation, no boilerplate. From idea to backtest in minutes.

Rich Ecosystem

pandas, numpy, scipy, scikit-learn, PyTorch, TA-Lib — everything you need, battle-tested.

Broker APIs

Alpaca, Interactive Brokers, Tradier — all have first-class Python SDKs for live trading.

ML Native

sklearn, XGBoost, TensorFlow, PyTorch. When you're ready for ML, Python is already there.

Community

QuantConnect, Zipline, Backtrader — massive open-source community solving the same problems.

Visualization

Matplotlib, Plotly, seaborn — debug your strategies visually. See patterns humans miss in numbers.

Python vs. C++ vs. R

C++ is faster for high-frequency (microsecond) execution, but for strategies with holding periods of minutes to months, Python's speed is more than adequate. R has better statistical libraries but weaker production/deployment tooling. Python sits in the sweet spot: fast enough for 99% of strategies, with the best ecosystem for going from research to production.

LanguageSpeedEcosystemLive TradingML SupportBest For
PythonMediumExcellentExcellentBestFull-stack quant
C++FastestLimitedGoodLimitedHFT execution
RSlowGood (stats)PoorGoodResearch only
JuliaFastGrowingLimitedGoodNumerical research
MATLABMediumGood (math)PoorLimitedAcademia
Environment Setup

Setting Up Your Python Environment

A clean, isolated environment is critical. You don't want your trading code fighting with other Python projects over package versions. We'll use conda (recommended for beginners) or venv (lighter, for experienced developers).

Option A: Conda (Recommended)

Conda manages both Python versions and system-level dependencies (like TA-Lib's C library). Install Miniconda (lightweight) rather than full Anaconda.

Terminal — Conda Setup
# Install Miniconda (macOS/Linux)
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh

# Create a dedicated environment
conda create -n quant python=3.11 -y
conda activate quant

# Install core packages
conda install pandas numpy matplotlib jupyter -y
conda install -c conda-forge ta-lib -y

# Install trading-specific packages via pip
pip install yfinance mplfinance alpaca-trade-api

Option B: venv + pip

If you prefer a lighter approach without conda. You'll need to install TA-Lib's C library separately.

Terminal — venv Setup
# Create and activate virtual environment
python3.11 -m venv quant-env
source quant-env/bin/activate  # Linux/macOS
# quant-env\Scripts\activate  # Windows

# Install everything via pip
pip install pandas numpy matplotlib jupyter
pip install yfinance mplfinance ta-lib
pip install alpaca-trade-api scikit-learn xgboost

IDE: VS Code or Jupyter?

Both. Use Jupyter notebooks (.ipynb) for exploration, visualization, and quick experiments. Use VS Code with the Python extension for production code — your backtest engine, risk manager, and execution loop. VS Code can also run Jupyter cells inline.

Terminal — VS Code Extensions
# Essential VS Code extensions for quant work
code --install-extension ms-python.python
code --install-extension ms-toolsai.jupyter
code --install-extension ms-python.pylint
code --install-extension ms-python.black-formatter

Common Pitfall: TA-Lib Installation

TA-Lib requires a C library that pip install ta-lib alone won't provide. On macOS: brew install ta-lib. On Ubuntu: sudo apt install libta-lib-dev. On Windows: download the pre-built binary from the TA-Lib website. Conda handles this automatically, which is why we recommend it for beginners.

What about Google Colab?
Colab is fine for learning and quick experiments, but it has limitations for serious quant work: sessions time out after 90 minutes of inactivity, you can't run scheduled jobs, and the free tier has limited resources. Start with Colab if you want zero setup, but plan to move to a local environment for Parts 4-6 of this series (risk management, live trading, ML).
Essential Libraries

The Quant Python Stack

Here's the complete library stack we'll use throughout this series. Install them all now — you'll need every one.

requirements.txt
# Core data manipulation
pandas==2.2.0
numpy==1.26.3

# Market data
yfinance==0.2.36

# Technical analysis
ta-lib==0.4.28
ta==0.11.0           # pure-Python fallback if TA-Lib fails

# Visualization
matplotlib==3.8.2
mplfinance==0.12.10  # candlestick charts
plotly==5.18.0        # interactive charts

# ML & Statistics (Part 6)
scikit-learn==1.4.0
xgboost==2.0.3
scipy==1.12.0
statsmodels==0.14.1

# Broker API (Part 5)
alpaca-trade-api==3.2.0

# Utilities
jupyter==1.0.0
python-dotenv==1.0.0  # API keys management
tqdm==4.66.1          # progress bars
LibraryPurposeUsed InWhy This One
pandasDataFrames, time seriesAll partsIndustry standard, vectorized operations
numpyNumerical computingAll parts10-100x faster than pure Python loops
yfinanceFree market dataParts 1-4Free, reliable, Yahoo Finance wrapper
TA-Lib150+ technical indicatorsParts 2-3C-based, fastest indicator library
matplotlibStatic visualizationAll partsMost flexible plotting library
mplfinanceCandlestick chartsParts 1-3Purpose-built for OHLCV data
scikit-learnML modelsPart 6Best API design, huge model zoo
xgboostGradient boostingPart 6Best tabular data model, top Kaggle tool
alpaca-trade-apiLive executionPart 5Free paper trading, modern REST API
scipyStatistics, optimizationParts 4, 6Portfolio optimization, distribution fitting

Why yfinance and Not a Paid API?

For learning and backtesting, yfinance is perfect: free, covers most US equities, ETFs, crypto, and provides daily/weekly OHLCV data plus fundamentals. Its limitations — delayed data, occasional rate limiting, no tick data — only matter for live intraday trading (Part 5). When you go live, you'll switch to your broker's API (Alpaca provides free real-time data). Don't pay for data until you have a strategy worth running.

Python — Verify Your Installation
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt

print(f"pandas:     {pd.__version__}")
print(f"numpy:      {np.__version__}")
print(f"yfinance:   {yf.__version__}")
print(f"matplotlib: {plt.matplotlib.__version__}")

# Quick smoke test: download 1 day of AAPL
test = yf.download("AAPL", period="1d")
print(f"\nSmoke test: {len(test)} rows downloaded")
print("All systems go!")
First Data Pull

Your First Data Pull: AAPL OHLCV

Let's download real market data and understand its structure. We'll work with AAPL (Apple Inc.) — the most liquid stock on the planet, with clean data and no survivorship bias concerns.

Python — Download AAPL Data
import yfinance as yf
import pandas as pd

# Download 5 years of daily OHLCV data
aapl = yf.download(
    "AAPL",
    start="2021-01-01",
    end="2026-01-01",
    auto_adjust=True  # Adjust for splits and dividends
)

# What did we get?
print(f"Shape: {aapl.shape}")       # (1258, 5) — ~252 trading days/year
print(f"Columns: {list(aapl.columns)}")
print(f"Date range: {aapl.index[0]} to {aapl.index[-1]}")
print(f"\nFirst 5 rows:")
print(aapl.head())

# Output:
# Shape: (1258, 5)
# Columns: ['Open', 'High', 'Low', 'Close', 'Volume']
# Date range: 2021-01-04 to 2025-12-31
#
#                  Open      High       Low     Close      Volume
# Date
# 2021-01-04   133.520    133.610   126.760   129.410   143301900
# 2021-01-05   128.890    131.740   128.430   131.010    97664900
# ...

Understanding OHLCV

Open: First trade price of the day. High: Highest price reached. Low: Lowest price reached. Close: Last trade price — this is the most important for backtesting. Volume: Total shares traded. The auto_adjust=True flag adjusts historical prices for stock splits and dividends, so your backtest results are accurate. Always use adjusted data for backtesting.

Python — Download Multiple Tickers
# Download multiple tickers at once
tickers = ["AAPL", "MSFT", "GOOGL", "AMZN", "NVDA",
           "SPY", "QQQ", "GLD", "TLT", "BTC-USD"]

data = yf.download(
    tickers,
    start="2021-01-01",
    end="2026-01-01",
    auto_adjust=True,
    group_by="ticker"  # Group columns by ticker
)

# Extract closing prices into a clean DataFrame
closes = pd.DataFrame({
    ticker: data[ticker]["Close"]
    for ticker in tickers
})

print(f"Tickers: {list(closes.columns)}")
print(f"Shape: {closes.shape}")
print(f"\nMissing values per ticker:")
print(closes.isnull().sum())

# Forward-fill missing values (weekends/holidays for crypto)
closes = closes.ffill().dropna()
Python — Save Data Locally
import os

# Save to CSV for offline use (don't re-download every time)
os.makedirs("data", exist_ok=True)

# Save individual ticker data
aapl.to_csv("data/AAPL_daily.csv")

# Save multi-ticker closes
closes.to_csv("data/universe_closes.csv")

# Load it back
aapl_loaded = pd.read_csv(
    "data/AAPL_daily.csv",
    index_col="Date",
    parse_dates=True
)

print(f"Loaded {len(aapl_loaded)} rows from disk")

Rate Limiting

yfinance scrapes Yahoo Finance. If you hammer it with requests, you'll get rate-limited or blocked. Best practice: download data once, save to CSV, and load from disk during development. Only re-download when you need fresh data. We'll build a proper data manager in Part 2.

Data Exploration

Exploring Your Data

Before building any strategy, understand your data. These basic statistics reveal the character of a stock — its volatility, trend, and trading behavior.

Python — Basic Statistics
import numpy as np

# Daily returns
aapl["returns"] = aapl["Close"].pct_change()

# Key statistics
stats = {
    "Total Return": f"{(aapl['Close'].iloc[-1] / aapl['Close'].iloc[0] - 1) * 100:.1f}%",
    "Annualized Return": f"{aapl['returns'].mean() * 252 * 100:.1f}%",
    "Annualized Vol": f"{aapl['returns'].std() * np.sqrt(252) * 100:.1f}%",
    "Sharpe Ratio": f"{(aapl['returns'].mean() / aapl['returns'].std()) * np.sqrt(252):.2f}",
    "Max Drawdown": f"{((aapl['Close'] / aapl['Close'].cummax()) - 1).min() * 100:.1f}%",
    "Best Day": f"{aapl['returns'].max() * 100:.1f}%",
    "Worst Day": f"{aapl['returns'].min() * 100:.1f}%",
    "Avg Daily Volume": f"{aapl['Volume'].mean():,.0f}",
    "% Positive Days": f"{(aapl['returns'] > 0).mean() * 100:.1f}%",
}

for key, val in stats.items():
    print(f"{key:<22} {val}")
Sharpe Ratio
Sharpe = (R_p - R_f) / sigma_p = (mean daily return / std daily return) x sqrt(252)
Python — Candlestick Chart with mplfinance
import mplfinance as mpf

# Last 60 trading days — candlestick with volume
mpf.plot(
    aapl.tail(60),
    type="candle",
    volume=True,
    title="AAPL — Last 60 Trading Days",
    style="charles",
    figsize=(14, 8),
    mav=(20,),  # 20-day moving average
    savefig="charts/aapl_candles.png"
)
Python — Return Distribution
import matplotlib.pyplot as plt
from scipy import stats

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Histogram of daily returns
axes[0].hist(aapl["returns"].dropna(), bins=100, alpha=0.7,
           color="#d97706", edgecolor="white")
axes[0].axvline(0, color="red", linestyle="--", alpha=0.5)
axes[0].set_title("Daily Return Distribution")
axes[0].set_xlabel("Daily Return")

# QQ plot — are returns normally distributed?
stats.probplot(aapl["returns"].dropna(), plot=axes[1])
axes[1].set_title("QQ Plot — Normal Distribution Test")

plt.tight_layout()
plt.savefig("charts/aapl_returns.png", dpi=150)
plt.show()

# Spoiler: returns are NOT normally distributed
# Fat tails (kurtosis > 3) mean extreme moves are more likely than a normal
# distribution predicts. This is why risk management (Part 4) matters.
print(f"Skewness: {aapl['returns'].skew():.3f}")   # Negative = more crash risk
print(f"Kurtosis: {aapl['returns'].kurtosis():.3f}")  # >3 = fat tails

Fat Tails Matter

Stock returns are NOT normally distributed. They have fat tails (extreme moves happen more often than a bell curve predicts) and negative skew (crashes are bigger than rallies). A "6-sigma" event should happen once every 1.5 million years under a normal distribution — but in real markets, they happen every few years. This is why position sizing and stop losses (Part 4) are non-negotiable.

Project Structure

Structuring Your Quant Project

Good project structure separates research from production, prevents data leakage, and makes collaboration easier. Here's the structure we'll build throughout this series:

Project Layout
quant-lab/
|
|-- data/                    # Raw and processed market data
|   |-- raw/                 # Original downloads (never modify)
|   |-- processed/           # Cleaned, with indicators added
|   |-- cache/               # yfinance cache to avoid re-downloads
|
|-- strategies/              # Strategy implementations
|   |-- base.py              # Abstract strategy class
|   |-- sma_crossover.py     # Part 2: SMA 20/50
|   |-- rsi_reversion.py     # Part 2: RSI mean reversion
|   |-- multi_factor.py      # Part 3: Combined signals
|
|-- backtests/               # Backtesting engine
|   |-- engine.py            # Core backtest engine (Part 2)
|   |-- metrics.py           # Sharpe, drawdown, etc.
|   |-- results/             # Saved backtest results (JSON/CSV)
|
|-- signals/                 # Signal generation (Part 3)
|   |-- momentum.py          # ROC, RSI, MACD, ADX
|   |-- reversion.py         # Bollinger, z-score, pairs
|   |-- volume.py            # OBV, VWAP, spikes
|   |-- composite.py         # Multi-factor scoring
|
|-- risk/                    # Risk management (Part 4)
|   |-- position_sizer.py    # Kelly, fixed fractional
|   |-- portfolio.py         # Correlation, optimization
|   |-- var.py               # VaR, Expected Shortfall
|
|-- live/                    # Live trading (Part 5)
|   |-- executor.py          # Order execution via Alpaca
|   |-- monitor.py           # Real-time PnL tracking
|   |-- logger.py            # Trade journal
|
|-- ml/                      # Machine learning (Part 6)
|   |-- features.py          # Feature engineering
|   |-- models.py            # RF, XGBoost, LSTM
|   |-- evaluation.py        # Out-of-sample testing
|
|-- notebooks/               # Jupyter notebooks for exploration
|   |-- 01_data_exploration.ipynb
|   |-- 02_backtest_sma.ipynb
|   |-- ...
|
|-- config/                  # Configuration
|   |-- settings.py          # Constants, paths
|   |-- universe.py          # Ticker lists
|
|-- .env                     # API keys (NEVER commit!)
|-- .gitignore               # Ignore .env, data/cache/, __pycache__
|-- requirements.txt         # Pinned dependencies
|-- README.md
Python — Scaffold the Project
import os

dirs = [
    "data/raw", "data/processed", "data/cache",
    "strategies", "backtests/results",
    "signals", "risk", "live", "ml",
    "notebooks", "config", "charts"
]

for d in dirs:
    os.makedirs(d, exist_ok=True)
    # Create __init__.py for Python packages
    if d not in ["data/raw", "data/processed", "data/cache",
                  "notebooks", "charts", "backtests/results"]:
        with open(os.path.join(d, "__init__.py"), "w") as f:
            f.write("")

print("Project scaffolded!")
.gitignore
# Secrets
.env
*.key

# Data (too large for git)
data/raw/
data/cache/
*.csv

# Python
__pycache__/
*.pyc
.pytest_cache/

# Jupyter checkpoints
.ipynb_checkpoints/

# IDE
.vscode/
.idea/

# OS
.DS_Store
Thumbs.db
config/settings.py — Central Configuration
from pathlib import Path
from dotenv import load_dotenv
import os

load_dotenv()

# Paths
ROOT = Path(__file__).parent.parent
DATA_RAW = ROOT / "data" / "raw"
DATA_PROCESSED = ROOT / "data" / "processed"
RESULTS = ROOT / "backtests" / "results"
CHARTS = ROOT / "charts"

# Trading constants
TRADING_DAYS_PER_YEAR = 252
RISK_FREE_RATE = 0.05      # Current 1Y Treasury ~5%
DEFAULT_COMMISSION = 0.001  # 10 bps per trade
DEFAULT_SLIPPAGE = 0.0005   # 5 bps slippage

# API Keys (from .env)
ALPACA_KEY = os.getenv("ALPACA_API_KEY")
ALPACA_SECRET = os.getenv("ALPACA_SECRET_KEY")
ALPACA_BASE_URL = os.getenv("ALPACA_BASE_URL", "https://paper-api.alpaca.markets")
config/universe.py — Ticker Universe
# Define your investment universe
# Start small — you can always add more later

MEGA_CAPS = ["AAPL", "MSFT", "GOOGL", "AMZN", "NVDA", "META", "TSLA"]
INDICES = ["SPY", "QQQ", "DIA", "IWM"]
SECTORS = ["XLF", "XLE", "XLK", "XLV", "XLI", "XLC", "XLRE"]
SAFE_HAVEN = ["GLD", "TLT", "SHY"]
CRYPTO = ["BTC-USD", "ETH-USD"]

# Full universe for backtesting
UNIVERSE = MEGA_CAPS + INDICES + SECTORS + SAFE_HAVEN + CRYPTO

Why Separate Raw and Processed Data?

The data/raw/ directory holds original downloads — never modify these files. The data/processed/ directory holds data with added indicators, cleaned NaN values, and derived features. This separation is crucial: if you discover a bug in your feature engineering, you can reprocess from raw data without re-downloading. It also prevents a subtle form of data leakage.

Roadmap

Series Roadmap

Here's the complete journey from zero to a live quantitative trading system:

1

Setup

Environment, libraries, data, project structure. You are here.

2

Backtest

Build a backtest engine from scratch. SMA crossover + RSI strategies.

3

Signals

Momentum, mean reversion, volume, volatility. Multi-factor models.

4

Risk

Position sizing, Kelly criterion, VaR, drawdown control, portfolio optimization.

5

Live

Alpaca API, paper trading, execution loop, monitoring, going live.

6

ML

Random Forest, XGBoost, feature engineering, avoiding overfitting.

Part 1 Checklist

Next: Part 2 of 6
Backtesting Engine — Test Before You Risk
Quant Trading in Python1/6