0% found this document useful (0 votes)
2 views56 pages

Quant Projects Python Bible

The 'Quant Trader: Python Projects Bible' is a comprehensive guide designed to help users build automated trading systems using Python, starting from the basics. It includes eight complete projects covering various trading tools and techniques, with no prior Python knowledge required. The guide provides step-by-step instructions for setting up the Python environment, downloading market data, and creating interactive charts, ensuring users can develop a fully functional trading system by the end.

Uploaded by

rajakarslayer
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views56 pages

Quant Projects Python Bible

The 'Quant Trader: Python Projects Bible' is a comprehensive guide designed to help users build automated trading systems using Python, starting from the basics. It includes eight complete projects covering various trading tools and techniques, with no prior Python knowledge required. The guide provides step-by-step instructions for setting up the Python environment, downloading market data, and creating interactive charts, ensuring users can develop a fully functional trading system by the end.

Uploaded by

rajakarslayer
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

QUANT TRADER
PYTHON PROJECTS BIBLE
Build Every Tool a Quant Trader Needs — From Scratch
────────────────────────────────────
Fair Value Gaps • Candle Crossovers • Interactive Charts
Sharpe Ratio Dashboard • Volatility Heatmaps • Live Backtesting
Automated Trade Execution • Options Greeks • Risk Management
8 Complete Projects • 2025 Edition

HOW TO USE THIS GUIDE


Every project in this guide is self-contained: you can read and build it independently. Each one starts
from the absolute basics of its topic and builds to a fully working system. No prior Python knowledge
is assumed — the first chapter covers setup from zero. All code is production-quality and fully
commented. By the end you will have 8 real projects on your GitHub, a live paper-trading system,
and interactive dashboards you can run on your own computer.

© 2025 Quant Projects Guide 1


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

CHAPTE
R0
Setting Up Your Python Trading Environment

Installing Everything You Need


Before writing a single line of trading code you need a working Python environment. This chapter
walks you through the complete setup — from downloading Python to having a working Jupyter
notebook with every library pre-installed.

Step 1 — Install Anaconda (Python + All Science Libraries)


Anaconda is a free distribution of Python that comes pre-bundled with hundreds of scientific
libraries. It is the industry standard for quantitative research.

1. Go to [Link]/download and download Anaconda for your operating system


(Windows, Mac, or Linux).
2. Run the installer — accept all defaults. This installs Python 3.11+ and creates a base
environment.
3. Open 'Anaconda Navigator' from your applications — you will see a launcher with various
tools.
4. Click 'Launch' under JupyterLab — your browser opens with a notebook environment.
5. Create a new notebook: click the blue '+' button, then 'Python 3 (ipykernel)' under Notebook.

Step 2 — Install Trading-Specific Libraries


Open a Terminal (Mac/Linux) or Anaconda Prompt (Windows) and run these commands one at a
time:

# Install all libraries needed for this guide


pip install yfinance # Download stock market data from Yahoo Finance
pip install pandas # Data manipulation and time series
pip install numpy # Numerical computing
pip install plotly # Interactive charts (key for dashboards)
pip install matplotlib # Static charts
pip install ta # Technical analysis indicators (RSI, MACD, etc.)
pip install alpaca-trade-api # Paper trading execution via Alpaca
pip install scipy # Scientific computing
pip install dash # Build web dashboards in Python
pip install statsmodels # Statistical models
pip install jupyter # Notebook environment

# Verify installation by importing them


python -c "import yfinance, pandas, numpy, plotly, ta, alpaca_trade_api;
print('All OK')"
# Output:
# All OK

© 2025 Quant Projects Guide 2


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

Step 3 — Understanding the Notebook


A Jupyter notebook is a document that mixes text and executable code. Each 'cell' can be run
independently.

• Press Shift+Enter to run a cell and move to the next one


• Press Ctrl+Enter to run a cell and stay on it
• Press Escape then 'b' to add a new cell below
• Press Escape then 'dd' to delete a cell
• A cell with [ ] has not been run. A cell with [*] is running. A cell with [1] has been run.

Step 4 — Your First Trading Script (Verify Everything Works)


# Run this in a Jupyter cell to verify all libraries work
import yfinance as yf
import pandas as pd
import numpy as np
import plotly.graph_objects as go

# Download Apple data


data = [Link]('AAPL', start='2023-01-01', end='2024-01-01', progress=False)

# Print the last 5 rows


print([Link]())
print(f"\nTotal rows: {len(data)}")

# Output (approximate):
# --- Open High Low Close Volume
# 2023-12-25 193.06 193.07 191.73 193.58 28919300
# 2023-12-26 193.35 193.75 191.77 192.49 28963400
# ...
# Total rows: 251

If you prefer a dedicated code editor over JupyterLab, install VS Code


VS Code ([Link]) and the Python + Jupyter extensions. VS Code gives you
Alternative better code completion, debugging, and file management while supporting
notebooks natively.

Understanding Pandas DataFrames — Your Data Container


Every piece of market data you will work with lives in a Pandas DataFrame. Understanding
DataFrames is the most important programming concept in this guide.

© 2025 Quant Projects Guide 3


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

What Is a DataFrame?
A DataFrame is like a spreadsheet inside Python. It has rows (indexed by date for market data) and
columns (Open, High, Low, Close, Volume). You can slice, filter, compute, and plot it.

import yfinance as yf
import pandas as pd

# Download 1 year of SPY (S&P 500 ETF)


df = [Link]('SPY', start='2023-01-01', end='2024-01-01', progress=False)

# ── Basic exploration ──────────────────────────────────────────


print([Link]) # (251, 5) — 251 rows, 5 columns
print([Link]())# ['Open', 'High', 'Low', 'Close', 'Volume']
print([Link]) # Shows data types of each column
print([Link](3)) # First 3 rows
print([Link](3)) # Last 3 rows
print([Link]()) # Summary stats: mean, std, min, max, quartiles

# ── Accessing columns ──────────────────────────────────────────


close_prices = df['Close'] # A Series (single column)
ohlc = df[['Open','High','Low','Close']] # A DataFrame (multiple columns)

# ── Accessing rows by date ─────────────────────────────────────


row = [Link]['2023-06-15'] # Get specific date
range_ = [Link]['2023-06-01':'2023-06-30'] # Date range

# ── Creating new columns ───────────────────────────────────────


df['Return'] = df['Close'].pct_change() # Daily % return
df['Range'] = df['High'] - df['Low'] # Candle range
df['MA20'] = df['Close'].rolling(20).mean() # 20-day moving average
df['MA50'] = df['Close'].rolling(50).mean()

# ── Filtering rows ─────────────────────────────────────────────


positive_days = df[df['Return'] > 0] # Only positive days
big_range_days = df[df['Range'] > 5] # Large candle days

print(f"Positive days: {len(positive_days)} out of {len(df)}")

© 2025 Quant Projects Guide 4


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

CHAPTE
R1
Getting Market Data — Every Timeframe

Downloading Daily, Hourly, and 5-Minute Data


Different trading strategies need different timeframes. A fair value gap strategy may use 1-hour or
5-minute candles. A long-term trend strategy uses daily. Here is how to download any timeframe.

import yfinance as yf
import pandas as pd

# ── Valid interval values ──────────────────────────────────────


# '1m' = 1 minute (last 7 days only)
# '2m' = 2 minutes (last 60 days)
# '5m' = 5 minutes (last 60 days) ← most useful for intraday
# '15m' = 15 minutes (last 60 days)
# '30m' = 30 minutes (last 60 days)
# '60m' = 1 hour (last 730 days ~2 years)
# '1h' = 1 hour (last 730 days)
# '1d' = 1 day (any start date)
# '1wk' = 1 week (any start date)

# ── 5-minute data (last 30 days) ──────────────────────────────


df_5m = [Link]('SPY', period='30d', interval='5m', progress=False)
print(df_5m.head())
print(f'5m rows: {len(df_5m)}') # ~1560 rows for 30 days

# ── 1-hour data (last 2 years) ─────────────────────────────────


df_1h = [Link]('SPY', period='730d', interval='1h', progress=False)
print(f'1h rows: {len(df_1h)}') # ~3900 rows for 2 years

# ── Daily data (5 years) ───────────────────────────────────────


df_1d = [Link]('SPY', start='2019-01-01', end='2024-01-01', interval='1d',
progress=False)
print(f'Daily rows: {len(df_1d)}') # ~1260 rows for 5 years

# ── Multiple tickers at once ───────────────────────────────────


tickers = ['AAPL', 'MSFT', 'NVDA', 'SPY', 'QQQ']
multi = [Link](tickers, start='2023-01-01', end='2024-01-01',
progress=False)
close_all = multi['Close'] # DataFrame: each column is one ticker
print(close_all.tail())

# ── Save to CSV so you don't re-download every time ───────────


df_1d.to_csv('SPY_daily.csv')
df_loaded = pd.read_csv('SPY_daily.csv', index_col='Date', parse_dates=True)

Handling Missing Data and Market Hours


import pandas as pd

© 2025 Quant Projects Guide 5


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

import yfinance as yf

df = [Link]('SPY', period='30d', interval='5m', progress=False)

# ── Remove pre-market and after-hours data ─────────────────────


# US market hours: 9:30am - 4:00pm Eastern
[Link] = pd.to_datetime([Link]) # Ensure datetime index

# Filter to regular trading hours only


market_hours = df.between_time('09:30', '16:00')
print(f'All rows: {len(df)}, Market hours only: {len(market_hours)}')

# ── Check for and remove NaN values ───────────────────────────


print([Link]().sum()) # Count NaN in each column
df_clean = [Link]() # Remove any row with NaN

# ── Check for duplicate timestamps ────────────────────────────


dupes = [Link]().sum()
print(f'Duplicate timestamps: {dupes}')
df = df[~[Link](keep='first')] # Keep first occurrence

# ── Resample: convert 5m to 15m candles ──────────────────────


df_15m = [Link]('15min').agg({
'Open': 'first', # First 5m open = 15m open
'High': 'max', # Highest high of the 3 x 5m candles
'Low': 'min', # Lowest low
'Close': 'last', # Last 5m close = 15m close
'Volume':'sum', # Sum of all volume
}).dropna()
print(f'5m rows: {len(df)}, 15m rows: {len(df_15m)}')

© 2025 Quant Projects Guide 6


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

CHAPTE Interactive Charts with Plotly — Candlesticks,


R2 Indicators & Dashboards

Why Plotly Instead of Matplotlib


Matplotlib makes static images. Plotly makes interactive charts you can zoom, pan, hover over, and
add/remove traces — like TradingView inside Python. For quant work, interactive charts are
essential because you need to inspect specific candles and signals closely.

Your First Interactive Candlestick Chart


import yfinance as yf
import plotly.graph_objects as go
from [Link] import make_subplots

# ── Download data ─────────────────────────────────────────────


df = [Link]('SPY', period='60d', interval='5m', progress=False)
df = df.between_time('09:30', '16:00') # Market hours only

# ── Build candlestick chart ────────────────────────────────────


fig = [Link](data=[[Link](
x=[Link],
open=df['Open'],
high=df['High'],
low=df['Low'],
close=df['Close'],
name='SPY',
increasing_line_color='#00b894', # Green candles
decreasing_line_color='#d63031', # Red candles
)])

# ── Layout styling ────────────────────────────────────────────


fig.update_layout(
title='SPY — 5-Minute Chart',
xaxis_title='Date & Time',
yaxis_title='Price ($)',
xaxis_rangeslider_visible=False, # Hide the tiny range slider
template='plotly_dark', # Dark background
height=600,
font=dict(family='Arial', size=12),
)

[Link]() # Opens in browser — fully interactive!


# You can: zoom in/out, pan, hover for exact OHLC values

Adding Moving Averages to the Chart


import yfinance as yf

© 2025 Quant Projects Guide 7


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

import plotly.graph_objects as go
import pandas as pd

df = [Link]('SPY', period='90d', interval='1h', progress=False)


df = df.between_time('09:30','16:00')

# ── Calculate indicators ──────────────────────────────────────


df['EMA9'] = df['Close'].ewm(span=9, adjust=False).mean()
df['EMA21'] = df['Close'].ewm(span=21, adjust=False).mean()
df['SMA50'] = df['Close'].rolling(50).mean()

# ── Build chart with multiple traces ──────────────────────────


fig = [Link]()

# Candlesticks
fig.add_trace([Link](
x=[Link], open=df['Open'], high=df['High'],
low=df['Low'], close=df['Close'], name='SPY',
increasing_line_color='#00b894',
decreasing_line_color='#e17055',
))

# EMA 9 line
fig.add_trace([Link](
x=[Link], y=df['EMA9'],
line=dict(color='#fdcb6e', width=1.5),
name='EMA 9',
))

# EMA 21 line
fig.add_trace([Link](
x=[Link], y=df['EMA21'],
line=dict(color='#74b9ff', width=1.5),
name='EMA 21',
))

# SMA 50 line
fig.add_trace([Link](
x=[Link], y=df['SMA50'],
line=dict(color='#a29bfe', width=2, dash='dash'),
name='SMA 50',
))

fig.update_layout(
title='SPY Hourly — EMA 9/21 + SMA 50',
template='plotly_dark',
xaxis_rangeslider_visible=False,
height=650,
)
[Link]()

Multi-Panel Chart: Price + Volume + RSI


import yfinance as yf
import plotly.graph_objects as go

© 2025 Quant Projects Guide 8


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

from [Link] import make_subplots


import ta

df = [Link]('SPY', period='90d', interval='1d', progress=False)

# ── Calculate indicators ──────────────────────────────────────


df['RSI'] = [Link](df['Close'], window=14).rsi()
df['EMA20']= df['Close'].ewm(span=20,adjust=False).mean()

# ── Create 3-row subplot layout ───────────────────────────────


fig = make_subplots(
rows=3, cols=1,
shared_xaxes=True, # All charts share the same x-axis
vertical_spacing=0.04,
row_heights=[0.55, 0.20, 0.25], # Price gets 55%, Vol 20%, RSI 25%
subplot_titles=['Price & EMA20', 'Volume', 'RSI (14)'],
)

# ── Row 1: Candlesticks + EMA ─────────────────────────────────


fig.add_trace([Link](
x=[Link], open=df['Open'], high=df['High'],
low=df['Low'], close=df['Close'], name='SPY',
increasing_line_color='#00b894',
decreasing_line_color='#e17055',
), row=1, col=1)

fig.add_trace([Link](
x=[Link], y=df['EMA20'],
line=dict(color='#fdcb6e', width=2),
name='EMA 20',
), row=1, col=1)

# ── Row 2: Volume bars ─────────────────────────────────────────


colors = ['#00b894' if c>=o else '#e17055'
for c,o in zip(df['Close'], df['Open'])]
fig.add_trace([Link](
x=[Link], y=df['Volume'],
marker_color=colors, name='Volume',
), row=2, col=1)

# ── Row 3: RSI + overbought/oversold lines ────────────────────


fig.add_trace([Link](
x=[Link], y=df['RSI'],
line=dict(color='#a29bfe', width=2),
name='RSI',
), row=3, col=1)

# Horizontal reference lines on RSI panel


fig.add_hline(y=70, line_dash='dash', line_color='#e17055',
annotation_text='Overbought 70', row=3, col=1)
fig.add_hline(y=30, line_dash='dash', line_color='#00b894',
annotation_text='Oversold 30', row=3, col=1)

fig.update_layout(
title='SPY — Complete Technical Dashboard',
template='plotly_dark',
xaxis_rangeslider_visible=False,
height=800, showlegend=True,

© 2025 Quant Projects Guide 9


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

)
[Link]()

Saving Charts as HTML Files (Shareable Interactive Charts)


# Save as interactive HTML — anyone can open it in a browser
fig.write_html('spy_chart.html')

# Save as static image (requires kaleido: pip install kaleido)


fig.write_image('spy_chart.png', width=1400, height=800, scale=2)

# Save as PDF
fig.write_image('spy_chart.pdf')

© 2025 Quant Projects Guide 10


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

CHAPTE
R3
Technical Indicators from Scratch

Building Every Indicator You Need


Technical indicators are mathematical transformations of price and volume data. For a quant trader
they serve as raw features for trading signals. Here we build the most important ones from scratch
in Python, then also show the library shortcut.

Moving Averages — SMA and EMA


import pandas as pd
import numpy as np
import yfinance as yf

df = [Link]('SPY', period='1y', interval='1d', progress=False)

# ── Simple Moving Average (SMA) ────────────────────────────────


# SMA(n) = average of last n closing prices
# All periods weighted equally
def sma(series, period):
return [Link](window=period).mean()

df['SMA_20'] = sma(df['Close'], 20)


df['SMA_50'] = sma(df['Close'], 50)
df['SMA_200'] = sma(df['Close'], 200)

# ── Exponential Moving Average (EMA) ──────────────────────────


# EMA gives more weight to recent prices
# Multiplier = 2 / (period + 1)
# EMA today = (Close today * multiplier) + (EMA yesterday * (1 - multiplier))
def ema(series, period):
return [Link](span=period, adjust=False).mean()

df['EMA_9'] = ema(df['Close'], 9)
df['EMA_21'] = ema(df['Close'], 21)
df['EMA_50'] = ema(df['Close'], 50)

# ── Crossover signal ──────────────────────────────────────────


# 1 when EMA9 crosses above EMA21 (bullish)
# -1 when EMA9 crosses below EMA21 (bearish)
df['EMA_above'] = df['EMA_9'] > df['EMA_21']
df['EMA_cross_up'] = (df['EMA_above']) &
(~df['EMA_above'].shift(1).fillna(False))
df['EMA_cross_down'] = (~df['EMA_above']) &
(df['EMA_above'].shift(1).fillna(True))

crosses_up = df[df['EMA_cross_up']].[Link]()
crosses_down = df[df['EMA_cross_down']].[Link]()
print(f'Bullish crosses: {len(crosses_up)}')
print(f'Bearish crosses: {len(crosses_down)}')

© 2025 Quant Projects Guide 11


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

RSI — Relative Strength Index


import pandas as pd
import numpy as np

def calculate_rsi(close, period=14):


'''
RSI = 100 - (100 / (1 + RS))
RS = Average Gain / Average Loss over last 'period' bars
RSI > 70 = overbought (potential sell signal)
RSI < 30 = oversold (potential buy signal)
'''
delta = [Link]() # Price changes
gain = [Link](lower=0) # Only positive changes
loss = (-delta).clip(lower=0) # Only negative changes (as
positive)

# Initial averages (simple mean for first period)


avg_gain = [Link](window=period).mean()
avg_loss = [Link](window=period).mean()

# Subsequent averages (smoothed)


for i in range(period, len(close)):
avg_gain.iloc[i] = (avg_gain.iloc[i-1] * (period-1) + [Link][i]) /
period
avg_loss.iloc[i] = (avg_loss.iloc[i-1] * (period-1) + [Link][i]) /
period

rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi

# ── Using the ta library (faster, same result) ─────────────────


import ta
df['RSI'] = [Link](df['Close'], window=14).rsi()

# ── RSI signals ───────────────────────────────────────────────


df['RSI_oversold'] = df['RSI'] < 30 # Potential buy
df['RSI_overbought']= df['RSI'] > 70 # Potential sell

# RSI crossing out of oversold: RSI went from <30 to >30


df['RSI_cross_30'] = (df['RSI'] > 30) & (df['RSI'].shift(1) <= 30)
df['RSI_cross_70'] = (df['RSI'] < 70) & (df['RSI'].shift(1) >= 70)

MACD — Moving Average Convergence Divergence


import pandas as pd
import ta

# MACD = EMA(12) - EMA(26)


# Signal Line = EMA(9) of MACD
# Histogram = MACD - Signal (bars showing momentum)

© 2025 Quant Projects Guide 12


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

def calculate_macd(close, fast=12, slow=26, signal=9):


ema_fast = [Link](span=fast, adjust=False).mean()
ema_slow = [Link](span=slow, adjust=False).mean()
macd_line = ema_fast - ema_slow
signal_line= macd_line.ewm(span=signal, adjust=False).mean()
histogram = macd_line - signal_line
return macd_line, signal_line, histogram

df['MACD'], df['MACD_signal'], df['MACD_hist'] = calculate_macd(df['Close'])

# ── MACD crossover signal ────────────────────────────────────


# Bullish: MACD line crosses ABOVE signal line
# Bearish: MACD line crosses BELOW signal line
df['MACD_bull'] = (df['MACD'] > df['MACD_signal']) & (df['MACD'].shift(1) <=
df['MACD_signal'].shift(1))
df['MACD_bear'] = (df['MACD'] < df['MACD_signal']) & (df['MACD'].shift(1) >=
df['MACD_signal'].shift(1))

# ── Or use the ta library shortcut ───────────────────────────


macd_obj = [Link](df['Close'], window_fast=12, window_slow=26,
window_sign=9)
df['MACD'] = macd_obj.macd()
df['MACD_sig'] = macd_obj.macd_signal()
df['MACD_hist']= macd_obj.macd_diff()

Bollinger Bands
import pandas as pd

def bollinger_bands(close, period=20, std_dev=2):


'''
Middle Band = SMA(20)
Upper Band = SMA(20) + 2 * StdDev(20)
Lower Band = SMA(20) - 2 * StdDev(20)
Price at upper band = potentially overbought
Price at lower band = potentially oversold
Bandwidth narrowing = low volatility, breakout coming
'''
middle = [Link](period).mean()
std = [Link](period).std()
upper = middle + std_dev * std
lower = middle - std_dev * std
return upper, middle, lower

df['BB_upper'], df['BB_mid'], df['BB_lower'] = bollinger_bands(df['Close'])

# ── Bandwidth: how wide the bands are (low = low vol) ─────────
df['BB_width'] = (df['BB_upper'] - df['BB_lower']) / df['BB_mid']

# ── %B: where price sits within the bands ─────────────────────


# %B = 0 means price is at lower band
# %B = 1 means price is at upper band
df['BB_pct'] = (df['Close'] - df['BB_lower']) / (df['BB_upper'] - df['BB_lower'])

# Touch of lower band (potential mean-reversion buy signal)


df['BB_lower_touch'] = df['Close'] <= df['BB_lower']

© 2025 Quant Projects Guide 13


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

df['BB_upper_touch'] = df['Close'] >= df['BB_upper']

ATR — Average True Range (Volatility Measure)


import pandas as pd
import numpy as np

def calculate_atr(high, low, close, period=14):


'''
True Range = max of:
1. High - Low (today's range)
2. |High - Previous Close| (gap up)
3. |Low - Previous Close| (gap down)
ATR = smoothed average of True Range
ATR is the most important volatility measure for position sizing
'''
prev_close = [Link](1)
tr1 = high - low
tr2 = (high - prev_close).abs()
tr3 = (low - prev_close).abs()
true_range = [Link]([tr1, tr2, tr3], axis=1).max(axis=1)
atr = true_range.ewm(span=period, adjust=False).mean()
return atr, true_range

df['ATR'], df['TR'] = calculate_atr(df['High'], df['Low'], df['Close'])

# ── ATR-based stop loss ──────────────────────────────────────


# A common approach: stop = entry - 1.5 * ATR
atr_multiple = 1.5
df['Stop_Long'] = df['Close'] - atr_multiple * df['ATR']
df['Stop_Short'] = df['Close'] + atr_multiple * df['ATR']

# ── ATR as % of price (normalised volatility) ────────────────


df['ATR_pct'] = (df['ATR'] / df['Close']) * 100
print(df[['Close','ATR','ATR_pct']].tail())

© 2025 Quant Projects Guide 14


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

PROJEC
T1
Fair Value Gap (FVG) Detection & Trading System

What Is a Fair Value Gap?


A Fair Value Gap (FVG) is a three-candle pattern from Smart Money Concepts (ICT) trading. It
identifies areas where price moved so quickly that it left an 'imbalance' — a gap between candles
where price did not fully trade. These gaps act as magnets: price often returns to fill them before
continuing in the original direction.

FVG DEFINITION: Bullish FVG: Candle 1's HIGH is BELOW Candle 3's LOW. There is a gap
between C1 high and C3 low that candle 2's body skipped over on the way up. The gap zone =
[C1 high, C3 low]. Bearish FVG: Candle 1's LOW is ABOVE Candle 3's HIGH. There is a gap
between C3 high and C1 low that price skipped over on the way down. The gap zone = [C3
high, C1 low].

Step 1 — Detect All FVGs in Historical Data


import yfinance as yf
import pandas as pd
import numpy as np

def detect_fvg(df, min_gap_pct=0.05):


'''
Detect all Bullish and Bearish Fair Value Gaps.
min_gap_pct: minimum gap size as % of price (filter tiny gaps)
Returns: list of dicts with gap info
'''
gaps = []

for i in range(1, len(df) - 1): # Candle 2 index


c1 = [Link][i - 1] # Candle 1 (left)
c2 = [Link][i] # Candle 2 (middle — the impulse)
c3 = [Link][i + 1] # Candle 3 (right)

# ── Bullish FVG: gap between C1 high and C3 low ────────


if c3['Low'] > c1['High']: # Gap exists
gap_size = c3['Low'] - c1['High']
gap_pct = gap_size / c1['High'] * 100

if gap_pct >= min_gap_pct: # Filter tiny gaps


[Link]({
'type' : 'bullish',
'time_c2' : [Link][i], # Time of the impulse candle
'top' : c3['Low'], # Upper boundary of gap
'bottom' : c1['High'], # Lower boundary of gap
'midpoint' : (c3['Low'] + c1['High']) / 2,
'size_pct' : round(gap_pct, 3),
'filled' : False,

© 2025 Quant Projects Guide 15


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

'fill_time' : None,
})

# ── Bearish FVG: gap between C3 high and C1 low ────────


if c3['High'] < c1['Low']: # Gap exists
gap_size = c1['Low'] - c3['High']
gap_pct = gap_size / c1['Low'] * 100

if gap_pct >= min_gap_pct:


[Link]({
'type' : 'bearish',
'time_c2' : [Link][i],
'top' : c1['Low'], # Upper boundary of gap
'bottom' : c3['High'], # Lower boundary of gap
'midpoint' : (c1['Low'] + c3['High']) / 2,
'size_pct' : round(gap_pct, 3),
'filled' : False,
'fill_time' : None,
})

return gaps

# ── Test on SPY 5-minute data ─────────────────────────────────


df = [Link]('SPY', period='30d', interval='5m', progress=False)
df = df.between_time('09:30', '16:00').dropna()

gaps = detect_fvg(df, min_gap_pct=0.03)


print(f'Total FVGs found: {len(gaps)}')

bullish = [g for g in gaps if g['type']=='bullish']


bearish = [g for g in gaps if g['type']=='bearish']
print(f'Bullish FVGs: {len(bullish)}')
print(f'Bearish FVGs: {len(bearish)}')

# Print first 3
for g in gaps[:3]:
print(f" {g['type'].upper()} at {g['time_c2']} | ",
f"Zone: {g['bottom']:.2f} – {g['top']:.2f} | ",
f"Size: {g['size_pct']:.2f}%")

Step 2 — Track Which Gaps Are Filled


def mark_filled_gaps(df, gaps):
'''
After detecting gaps, scan forward to see which ones price returned to fill.
A gap is considered 'filled' when price trades back into the gap zone.
'''
for gap in gaps:
# Find the index of the gap's candle
gap_idx = [Link].get_loc(gap['time_c2'])

# Search all subsequent candles


for j in range(gap_idx + 1, len(df)):
candle = [Link][j]

© 2025 Quant Projects Guide 16


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

if gap['type'] == 'bullish':
# Bullish gap is filled when price dips back into the zone
# i.e., candle low reaches below the gap top (upper boundary)
if candle['Low'] <= gap['top']:
gap['filled'] = True
gap['fill_time'] = [Link][j]
break

elif gap['type'] == 'bearish':


# Bearish gap is filled when price rallies back into the zone
if candle['High'] >= gap['bottom']:
gap['filled'] = True
gap['fill_time'] = [Link][j]
break

return gaps

gaps = mark_filled_gaps(df, gaps)

filled = [g for g in gaps if g['filled']]


unfilled = [g for g in gaps if not g['filled']]

fill_rate = len(filled) / len(gaps) * 100 if gaps else 0


print(f'Total gaps: {len(gaps)}')
print(f'Filled gaps: {len(filled)} ({fill_rate:.1f}%)')
print(f'Unfilled gaps: {len(unfilled)}')

# ── Time to fill (in candles) ─────────────────────────────────


for g in filled:
start_idx = [Link].get_loc(g['time_c2'])
end_idx = [Link].get_loc(g['fill_time'])
g['candles_to_fill'] = end_idx - start_idx

if filled:
avg_fill = sum(g['candles_to_fill'] for g in filled) / len(filled)
print(f'Average candles to fill: {avg_fill:.1f}')

Step 3 — Visualise FVGs on the Interactive Chart


import plotly.graph_objects as go

def plot_fvg_chart(df, gaps, last_n_candles=200):


'''Plot candlestick chart with FVG zones highlighted as rectangles.'''
df_plot = [Link][-last_n_candles:]

fig = [Link]()

# ── Candlesticks ──────────────────────────────────────────
fig.add_trace([Link](
x=df_plot.index,
open=df_plot['Open'], high=df_plot['High'],
low=df_plot['Low'], close=df_plot['Close'],
name='Price',
increasing_line_color='#00b894',
decreasing_line_color='#e17055',

© 2025 Quant Projects Guide 17


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

))

# ── Draw FVG zones as shaded rectangles ───────────────────


for gap in gaps:
# Only draw gaps that appear in the visible range
if gap['time_c2'] < df_plot.index[0]:
continue

color = 'rgba(0,184,148,0.15)' if gap['type']=='bullish' else


'rgba(225,112,85,0.15)'
border= 'rgba(0,184,148,0.6)' if gap['type']=='bullish' else
'rgba(225,112,85,0.6)'

# Rectangle from gap creation to end of chart


x_end = df_plot.index[-1] if not gap['filled'] else gap['fill_time']

fig.add_shape(
type='rect',
x0=gap['time_c2'], x1=x_end,
y0=gap['bottom'], y1=gap['top'],
fillcolor=color,
line=dict(color=border, width=1, dash='dot'),
)

# Label the gap


fig.add_annotation(
x=gap['time_c2'],
y=gap['midpoint'],
text=f"{'Bull' if gap['type']=='bullish' else 'Bear'} FVG
{gap['size_pct']:.2f}%",
showarrow=False,
font=dict(size=9, color=border),
bgcolor='rgba(0,0,0,0.5)',
)

fig.update_layout(
title='Fair Value Gaps — SPY 5-min',
template='plotly_dark',
xaxis_rangeslider_visible=False,
height=700,
)
[Link]()

plot_fvg_chart(df, gaps)

Step 4 — The FVG Trading Strategy (Entry Logic)


import pandas as pd
import numpy as np

def fvg_strategy(df, gaps, risk_reward=2.0, atr_stop_mult=1.0):


'''
FVG Re-entry Strategy:
- When price returns to a BULLISH FVG zone from above → BUY
- When price returns to a BEARISH FVG zone from below → SELL SHORT
- Stop Loss: 1 ATR below entry (for longs)

© 2025 Quant Projects Guide 18


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

- Take Profit: Risk * risk_reward above entry


'''
trades = []

# Calculate ATR for stop sizing


df = [Link]()
prev_close = df['Close'].shift(1)
tr = [Link]([
df['High'] - df['Low'],
(df['High'] - prev_close).abs(),
(df['Low'] - prev_close).abs()
], axis=1).max(axis=1)
df['ATR'] = [Link](span=14, adjust=False).mean()

in_trade = False
current_trade = None

for i in range(1, len(df)):


candle = [Link][i]
prev = [Link][i - 1]
ts = [Link][i]

# ── Manage open trade ─────────────────────────────────


if in_trade:
t = current_trade

if t['side'] == 'long':
if candle['Low'] <= t['stop']: # Stop hit
t['exit_price'] = t['stop']
t['exit_time'] = ts
t['exit_type'] = 'stop'
t['pnl'] = t['exit_price'] - t['entry_price']
[Link](t)
in_trade = False
elif candle['High'] >= t['target']: # Target hit
t['exit_price'] = t['target']
t['exit_time'] = ts
t['exit_type'] = 'target'
t['pnl'] = t['exit_price'] - t['entry_price']
[Link](t)
in_trade = False

elif t['side'] == 'short':


if candle['High'] >= t['stop']:
t['exit_price'] = t['stop']
t['exit_time'] = ts
t['exit_type'] = 'stop'
t['pnl'] = t['entry_price'] - t['exit_price']
[Link](t)
in_trade = False
elif candle['Low'] <= t['target']:
t['exit_price'] = t['target']
t['exit_time'] = ts
t['exit_type'] = 'target'
t['pnl'] = t['entry_price'] - t['exit_price']
[Link](t)
in_trade = False

© 2025 Quant Projects Guide 19


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

# ── Look for new entries ───────────────────────────────


if not in_trade:
atr = candle['ATR']

for gap in gaps:


if gap['filled'] or gap['time_c2'] >= ts:
continue # Skip already-filled or future gaps

if gap['type'] == 'bullish':
# Price enters the gap zone from above = buy
if candle['Low'] <= gap['top'] and candle['Close'] >=
gap['bottom']:
entry = gap['top'] # Enter at top of gap
stop = entry - atr_stop_mult * atr
risk = entry - stop
target = entry + risk * risk_reward
current_trade = {
'side' : 'long',
'entry_time' : ts,
'entry_price': entry,
'stop' : stop,
'target' : target,
'risk' : risk,
'gap_type' : 'bullish',
}
in_trade = True
gap['filled'] = True # Mark as used
break

elif gap['type'] == 'bearish':


# Price enters the gap zone from below = short
if candle['High'] >= gap['bottom'] and candle['Close'] <=
gap['top']:
entry = gap['bottom']
stop = entry + atr_stop_mult * atr
risk = stop - entry
target = entry - risk * risk_reward
current_trade = {
'side' : 'short',
'entry_time' : ts,
'entry_price': entry,
'stop' : stop,
'target' : target,
'risk' : risk,
'gap_type' : 'bearish',
}
in_trade = True
gap['filled'] = True
break

return [Link](trades)

# ── Run the strategy ──────────────────────────────────────────


results = fvg_strategy(df, gaps, risk_reward=2.0, atr_stop_mult=1.0)
print(f'Total trades: {len(results)}')
if len(results) > 0:

print(results[['side','entry_time','entry_price','exit_type','pnl']].head(10))

© 2025 Quant Projects Guide 20


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

© 2025 Quant Projects Guide 21


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

PROJEC
T2
5-Minute Candle Crossover Trade Executor

The Strategy: EMA Cross on 5-Minute Candles


This project builds a complete system that monitors 5-minute candles and executes a trade the
moment a specific candle condition is met — in this case, when the EMA9 crosses above or below
the EMA21. The same structure can be adapted to any condition you want.

Step 1 — Define the Crossover Condition Precisely


import pandas as pd
import numpy as np

def generate_crossover_signals(df, fast_period=9, slow_period=21):


'''
Generate buy/sell signals when fast EMA crosses slow EMA.
Signal = +1 at the CLOSE of the crossover candle
Trade executes at the OPEN of the NEXT candle
This avoids look-ahead bias.
'''
df = [Link]()

# Calculate EMAs
df[f'EMA{fast_period}'] = df['Close'].ewm(span=fast_period,
adjust=False).mean()
df[f'EMA{slow_period}'] = df['Close'].ewm(span=slow_period,
adjust=False).mean()

# Is fast EMA above slow EMA?


df['fast_above'] = df[f'EMA{fast_period}'] > df[f'EMA{slow_period}']

# Crossover detection:
# Cross UP: fast was below (or equal) last bar, now above
# Cross DOWN: fast was above (or equal) last bar, now below
df['cross_up'] = df['fast_above'] &
~df['fast_above'].shift(1).fillna(False)
df['cross_down'] = ~df['fast_above'] & df['fast_above'].shift(1).fillna(True)

# Raw signal on this bar (we know this at close)


df['signal_raw'] = 0
[Link][df['cross_up'], 'signal_raw'] = 1 # Buy signal
[Link][df['cross_down'], 'signal_raw'] = -1 # Sell signal

# Actual position: shifted by 1 (execute on next open)


df['position'] = df['signal_raw'].shift(1).fillna(0)

return df

# ── Test ──────────────────────────────────────────────────────
import yfinance as yf
df = [Link]('SPY', period='30d', interval='5m', progress=False)

© 2025 Quant Projects Guide 22


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

df = df.between_time('09:30','16:00').dropna()

df = generate_crossover_signals(df)

# Show rows where a signal fired


signals = df[df['signal_raw'] != 0][['Close','EMA9','EMA21','signal_raw']]
print(f'Total signals: {len(signals)}')
print([Link](10))

Step 2 — Add Filters to Improve Signal Quality


def add_filters(df, rsi_period=14, atr_period=14, volume_lookback=20):
'''
Add filters to only take crossover signals that meet additional conditions:
1. RSI filter: only buy when RSI > 50 (in uptrend), sell when RSI < 50
2. Volume filter: only trade when volume is above average (confirming move)
3. ATR filter: avoid trading when ATR is very low (no volatility = no move)
'''
df = [Link]()
import ta

# ── RSI ────────────────────────────────────────────────────
df['RSI'] = [Link](df['Close'], window=rsi_period).rsi()

# ── Volume filter ──────────────────────────────────────────


df['vol_avg'] = df['Volume'].rolling(volume_lookback).mean()
df['high_vol'] = df['Volume'] > df['vol_avg']

# ── ATR ────────────────────────────────────────────────────
prev_close = df['Close'].shift(1)
tr = [Link]([
df['High'] - df['Low'],
(df['High'] - prev_close).abs(),
(df['Low'] - prev_close).abs()
], axis=1).max(axis=1)
df['ATR'] = [Link](span=atr_period, adjust=False).mean()
df['atr_pct'] = df['ATR'] / df['Close'] * 100

# ── Filtered signal ────────────────────────────────────────


# Buy only if: cross up AND RSI > 50 AND high volume
# Sell only if: cross down AND RSI < 50 AND high volume
df['signal_filtered'] = 0

buy_cond = (df['signal_raw'] == 1) & (df['RSI'] > 50) & (df['high_vol'])


sell_cond = (df['signal_raw'] == -1) & (df['RSI'] < 50) & (df['high_vol'])

[Link][buy_cond, 'signal_filtered'] = 1
[Link][sell_cond, 'signal_filtered'] = -1

print(f'Raw signals: {(df["signal_raw"] != 0).sum()}')


print(f'Filtered signals: {(df["signal_filtered"] != 0).sum()}')

return df

df = add_filters(df)

© 2025 Quant Projects Guide 23


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

Step 3 — Visualise Signals on the Chart


import plotly.graph_objects as go
from [Link] import make_subplots

def plot_crossover_signals(df, n=200):


d = [Link][-n:].copy()

fig = make_subplots(rows=2, cols=1, shared_xaxes=True,


row_heights=[0.7,0.3], vertical_spacing=0.04)

# Candles
fig.add_trace([Link](
x=[Link], open=d['Open'], high=d['High'],
low=d['Low'], close=d['Close'],
name='SPY',
increasing_line_color='#00b894',
decreasing_line_color='#e17055',
), row=1, col=1)

# EMA lines
fig.add_trace([Link](x=[Link], y=d['EMA9'],
line=dict(color='#fdcb6e',width=1.5), name='EMA9'), row=1, col=1)
fig.add_trace([Link](x=[Link], y=d['EMA21'],
line=dict(color='#74b9ff',width=1.5), name='EMA21'), row=1, col=1)

# Buy signals (green triangles pointing up)


buys = d[d['signal_filtered'] == 1]
fig.add_trace([Link](
x=[Link], y=buys['Low'] * 0.999,
mode='markers',
marker=dict(symbol='triangle-up', size=12, color='#00b894'),
name='Buy Signal',
), row=1, col=1)

# Sell signals (red triangles pointing down)


sells = d[d['signal_filtered'] == -1]
fig.add_trace([Link](
x=[Link], y=sells['High'] * 1.001,
mode='markers',
marker=dict(symbol='triangle-down', size=12, color='#e17055'),
name='Sell Signal',
), row=1, col=1)

# RSI
fig.add_trace([Link](x=[Link], y=d['RSI'],
line=dict(color='#a29bfe',width=1.5), name='RSI'), row=2, col=1)
fig.add_hline(y=50, line_dash='dash', line_color='white', row=2, col=1)

fig.update_layout(
title='5-min EMA Crossover Signals (Filtered)',
template='plotly_dark',
xaxis_rangeslider_visible=False,
height=750,
)
[Link]()

© 2025 Quant Projects Guide 24


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

plot_crossover_signals(df)

Step 4 — Condition Builder: Any Condition You Want


The following function is a reusable 'condition engine.' You describe your entry condition in code
and it applies it to any DataFrame. Here are several common conditions:

def check_conditions(df):
'''
A library of trading conditions.
Each returns a boolean Series: True where condition is met.
'''
conditions = {}

# ── PRICE CONDITIONS ──────────────────────────────────────


# Price closes above a specific level
conditions['above_500'] = df['Close'] > 500

# Price crosses ABOVE a 20-period SMA


sma20 = df['Close'].rolling(20).mean()
conditions['cross_above_sma20'] = (df['Close'] > sma20) &
(df['Close'].shift(1) <= [Link](1))

# Price makes a new 5-period high (breakout)


conditions['new_5bar_high'] = df['Close'] == df['Close'].rolling(5).max()

# Inside bar: today's range is inside yesterday's range


conditions['inside_bar'] = (
(df['High'] < df['High'].shift(1)) &
(df['Low'] > df['Low'].shift(1))
)

# ── INDICATOR CONDITIONS ──────────────────────────────────


import ta
rsi = [Link](df['Close'], window=14).rsi()

# RSI crosses up through 30 (exit oversold)


conditions['rsi_cross_30'] = (rsi > 30) & ([Link](1) <= 30)

# RSI between 40 and 60 (neutral zone — avoid)


conditions['rsi_neutral'] = (rsi >= 40) & (rsi <= 60)

macd = [Link](df['Close'])
conditions['macd_cross_up'] = (
([Link]() > macd.macd_signal()) &
([Link]().shift(1) <= macd.macd_signal().shift(1))
)

# ── CANDLE PATTERN CONDITIONS ─────────────────────────────


# Bullish engulfing: green candle that engulfs prior red candle
is_green = df['Close'] > df['Open']
is_red = df['Close'] < df['Open']
conditions['bullish_engulf'] = (

© 2025 Quant Projects Guide 25


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

is_green &
is_red.shift(1) &
(df['Open'] < df['Close'].shift(1)) & # Opens below prior close
(df['Close'] > df['Open'].shift(1)) # Closes above prior open
)

# Doji: open ≈ close (body less than 10% of total range)


body = (df['Close'] - df['Open']).abs()
range_= df['High'] - df['Low']
conditions['doji'] = (body / range_.replace(0, [Link])) < 0.1

# ── VOLUME CONDITIONS ─────────────────────────────────────


conditions['volume_spike'] = df['Volume'] > df['Volume'].rolling(20).mean() *
1.5

# ── COMBINATION ───────────────────────────────────────────
# Custom combo: RSI cross out of oversold + MACD cross up + high volume
conditions['high_prob_buy'] = (
conditions['rsi_cross_30'] &
conditions['macd_cross_up'] &
conditions['volume_spike']
)

return conditions

# Apply all conditions


conds = check_conditions(df)
for name, series in [Link]():
count = [Link]()
print(f'{name:25s}: {count} signals')

© 2025 Quant Projects Guide 26


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

PROJEC Complete Backtesting Engine with Sharpe,


T3 Drawdown & Interactive Results

Building a Professional Backtester from Zero


A backtester simulates how a strategy would have performed historically. This project builds a
complete, reusable backtesting class that handles any strategy, calculates all key metrics, and
produces interactive performance dashboards.

The Backtester Class


import pandas as pd
import numpy as np
from typing import Callable

class Backtester:
'''
A complete backtesting engine.

Usage:
bt = Backtester(df, initial_capital=10000)
[Link](signal_series)
bt.print_stats()
[Link]()
'''

def __init__(self, df: [Link], initial_capital: float = 10_000,


commission: float = 0.0005, slippage: float = 0.0002):
'''
df : OHLCV DataFrame with DatetimeIndex
initial_capital: Starting portfolio value in dollars
commission : Per-trade cost as fraction of trade value (0.0005 =
0.05%)
slippage : Assumed fill slippage as fraction of price
'''
[Link] = [Link]()
[Link] = initial_capital
[Link] = commission
[Link] = slippage
[Link] = None

def run(self, signals: [Link]):


'''
signals: Series aligned with df, values: +1 (long), -1 (short), 0 (flat)
Signals must already be shifted (i.e. today's signal = tomorrow's
position)',
'''
df = [Link]()
df['signal'] = [Link]([Link]).fillna(0)

# ── Calculate returns ─────────────────────────────────

© 2025 Quant Projects Guide 27


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

df['mkt_ret'] = df['Close'].pct_change() # Market return


df['position'] = df['signal'].shift(1).fillna(0) # Yesterday signal =
today position

# Raw strategy return = position * market return


df['raw_ret'] = df['position'] * df['mkt_ret']

# ── Transaction costs ─────────────────────────────────


# A cost applies when position changes
df['trade'] = df['position'].diff().abs() # 0=no trade, non-
zero=trade
df['cost'] = df['trade'] * ([Link] + [Link])
df['strat_ret']= df['raw_ret'] - df['cost']

# ── Equity curves ────────────────────────────────────


df['equity'] = [Link] * (1 + df['strat_ret']).cumprod()
df['mkt_equity']= [Link] * (1 + df['mkt_ret']).cumprod()

# ── Drawdown ──────────────────────────────────────────
peak = df['equity'].cummax()
df['drawdown'] = (df['equity'] - peak) / peak

[Link] = df
return self

def get_stats(self) -> dict:


'''Calculate all performance statistics.'''
df = [Link]
rets = df['strat_ret'].dropna()
n_days_per_year = 252 if len(rets) > 100 else 252

# ── Returns ───────────────────────────────────────────
total_ret = df['equity'].iloc[-1] / [Link] - 1
n_years = len(rets) / n_days_per_year
ann_ret = (1 + total_ret) ** (1 / n_years) - 1 if n_years > 0 else 0
ann_vol = [Link]() * [Link](n_days_per_year)

# ── Risk-adjusted ─────────────────────────────────────
rf_daily = 0.05 / n_days_per_year # 5% risk-free rate
excess_ret = rets - rf_daily
sharpe = (excess_ret.mean() / [Link]()) *
[Link](n_days_per_year) if [Link]() > 0 else 0
sortino_denom= rets[rets < 0].std() * [Link](n_days_per_year)
sortino = (ann_ret - 0.05) / sortino_denom if sortino_denom > 0 else
0

# ── Drawdown ──────────────────────────────────────────
max_dd = df['drawdown'].min()
calmar = ann_ret / abs(max_dd) if max_dd != 0 else 0

# ── Trade stats ───────────────────────────────────────


trade_rets = rets[df['trade'].shift(1).fillna(0) != 0]
win_rate = (trade_rets > 0).mean() if len(trade_rets) > 0 else 0
profit_factor= trade_rets[trade_rets>0].sum() /
abs(trade_rets[trade_rets<0].sum()) \
if len(trade_rets[trade_rets<0]) > 0 else [Link]
n_trades = int(df['trade'].sum())

© 2025 Quant Projects Guide 28


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

# ── Benchmark comparison ──────────────────────────────


mkt_total = df['mkt_equity'].iloc[-1] / [Link] - 1
mkt_ann = (1 + mkt_total) ** (1/n_years) - 1 if n_years > 0 else 0
mkt_sharpe = (df['mkt_ret'].mean() / df['mkt_ret'].std()) *
[Link](n_days_per_year)

return {
'Total Return' : f'{total_ret:.2%}',
'Ann. Return' : f'{ann_ret:.2%}',
'Ann. Volatility' : f'{ann_vol:.2%}',
'Sharpe Ratio' : f'{sharpe:.3f}',
'Sortino Ratio' : f'{sortino:.3f}',
'Calmar Ratio' : f'{calmar:.3f}',
'Max Drawdown' : f'{max_dd:.2%}',
'Win Rate' : f'{win_rate:.2%}',
'Profit Factor' : f'{profit_factor:.2f}',
'N Trades' : n_trades,
'Mkt Total Ret' : f'{mkt_total:.2%}',
'Mkt Ann. Return' : f'{mkt_ann:.2%}',
'Mkt Sharpe' : f'{mkt_sharpe:.3f}',
}

def print_stats(self):
stats = self.get_stats()
print('\n' + '='*45)
print(' BACKTEST PERFORMANCE REPORT')
print('='*45)
for k, v in [Link]():
print(f' {k:22s}: {v}')
print('='*45)
return self

Interactive Performance Dashboard


import plotly.graph_objects as go
from [Link] import make_subplots
import numpy as np

def plot_backtest(bt: Backtester, title='Strategy Performance'):


'''
Full interactive dashboard showing:
- Equity curve vs buy-and-hold
- Drawdown chart
- Rolling Sharpe ratio
- Monthly returns heatmap
'''
df = [Link]
stats = bt.get_stats()

# ── Calculate rolling Sharpe (63-day = ~3 months) ─────────


roll_window = 63
roll_sharpe = (df['strat_ret'].rolling(roll_window).mean() /
df['strat_ret'].rolling(roll_window).std()) * [Link](252)

fig = make_subplots(

© 2025 Quant Projects Guide 29


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

rows=3, cols=1,
shared_xaxes=True,
vertical_spacing=0.06,
row_heights=[0.50, 0.25, 0.25],
subplot_titles=[
f'Equity Curve | Sharpe: {stats["Sharpe Ratio"]} | Max DD:
{stats["Max Drawdown"]}',
'Drawdown (%)',
'Rolling 63-Day Sharpe Ratio',
],
)

# ── Row 1: Equity curves ──────────────────────────────────


fig.add_trace([Link](
x=[Link], y=df['equity'],
line=dict(color='#00b894', width=2.5),
name=f'Strategy ({stats["Ann. Return"]} ann)',
fill='tonexty' if False else None,
), row=1, col=1)

fig.add_trace([Link](
x=[Link], y=df['mkt_equity'],
line=dict(color='#74b9ff', width=1.5, dash='dash'),
name=f'Buy & Hold ({stats["Mkt Ann. Return"]} ann)',
), row=1, col=1)

# ── Row 2: Drawdown ───────────────────────────────────────


fig.add_trace([Link](
x=[Link], y=df['drawdown'] * 100,
line=dict(color='#e17055', width=1.5),
fill='tozeroy', fillcolor='rgba(225,112,85,0.2)',
name='Drawdown',
), row=2, col=1)

# ── Row 3: Rolling Sharpe ─────────────────────────────────


colors_sharpe = ['#00b894' if v >= 0 else '#e17055' for v in
roll_sharpe.fillna(0)]
fig.add_trace([Link](
x=[Link], y=roll_sharpe,
marker_color=colors_sharpe,
name='Rolling Sharpe',
), row=3, col=1)
fig.add_hline(y=0, line_dash='dash', line_color='white', row=3, col=1)
fig.add_hline(y=1, line_dash='dot', line_color='#fdcb6e', row=3, col=1)

fig.update_layout(
title=title,
template='plotly_dark',
height=850,
showlegend=True,
legend=dict(yanchor='top', y=0.99, xanchor='left', x=0.01),
)
[Link]()

# ── Run everything together ───────────────────────────────────


import yfinance as yf

df = [Link]('SPY', start='2020-01-01', end='2024-01-01', progress=False)

© 2025 Quant Projects Guide 30


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

# Generate EMA crossover signals


ema9 = df['Close'].ewm(span=9, adjust=False).mean()
ema21 = df['Close'].ewm(span=21, adjust=False).mean()
fast_above = ema9 > ema21
signals = [Link](0, index=[Link])
signals[fast_above] = 1
signals[~fast_above] = -1

# Run backtest
bt = Backtester(df, initial_capital=10_000, commission=0.0005)
[Link](signals)
bt.print_stats()
plot_backtest(bt, 'SPY — EMA 9/21 Crossover Strategy')

Monthly Returns Heatmap


import plotly.graph_objects as go
import pandas as pd
import numpy as np

def plot_monthly_heatmap(bt: Backtester):


'''
Monthly returns heatmap: rows = years, columns = months.
Green = positive month, Red = negative month.',
'''
df = [Link]()

# Resample to monthly returns


monthly = df['strat_ret'].resample('ME').apply(
lambda x: (1 + x).prod() - 1
)

# Pivot into year x month matrix


monthly_df = [Link]({
'year' : [Link],
'month': [Link],
'ret' : [Link] * 100, # as percentage
})
pivot = monthly_df.pivot(index='year', columns='month', values='ret')
[Link] = ['Jan','Feb','Mar','Apr','May','Jun',
'Jul','Aug','Sep','Oct','Nov','Dec']

# ── Heatmap ──────────────────────────────────────────────
z_text = [[f'{v:.1f}%' if not [Link](v) else '' for v in row]
for row in [Link]]

fig = [Link](data=[Link](
z=[Link],
x=[Link](),
y=[str(y) for y in [Link]],
text=z_text,
texttemplate='%{text}',
colorscale=[
[0.0, '#c0392b'], # Dark red for worst

© 2025 Quant Projects Guide 31


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

[0.4, '#e17055'], # Light red


[0.5, '#dfe6e9'], # White/grey for zero
[0.6, '#00b894'], # Light green
[1.0, '#00695c'], # Dark green for best
],
zmid=0,
showscale=True,
colorbar=dict(title='Return (%)'),
))

fig.update_layout(
title='Monthly Returns Heatmap',
template='plotly_dark',
height=400,
xaxis=dict(side='top'),
)
[Link]()

plot_monthly_heatmap(bt)

© 2025 Quant Projects Guide 32


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

PROJEC
T4
Live Sharpe Ratio & Volatility Interactive Dashboard

Building a Full Analytics Dashboard


This project creates a comprehensive, interactive dashboard showing Sharpe ratio, rolling volatility,
and return distribution — all updating from live data. This is the kind of tool you use daily to monitor
your strategy.

import yfinance as yf
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from [Link] import make_subplots

class StrategyDashboard:
'''Interactive analytics dashboard for any strategy.'''

def __init__(self, ticker='SPY', period='2y', interval='1d'):


[Link] = [Link](ticker, period=period, interval=interval,
progress=False)
[Link] = ticker
self._compute()

def _compute(self):
df = [Link]
df['Return'] = df['Close'].pct_change()

# ── Volatility metrics ────────────────────────────────


# Realised vol = rolling std of daily returns, annualised
df['Vol_20'] = df['Return'].rolling(20).std() * [Link](252) * 100
df['Vol_60'] = df['Return'].rolling(60).std() * [Link](252) * 100
df['Vol_252'] = df['Return'].rolling(252).std() * [Link](252) * 100

# ── Rolling Sharpe ratio ──────────────────────────────


# Annualised Sharpe over rolling windows
rf_daily = 0.05 / 252
excess = df['Return'] - rf_daily
df['Sharpe_21'] = ([Link](21).mean() /
df['Return'].rolling(21).std()) * [Link](252)
df['Sharpe_63'] = ([Link](63).mean() /
df['Return'].rolling(63).std()) * [Link](252)
df['Sharpe_252'] = ([Link](252).mean() /
df['Return'].rolling(252).std()) * [Link](252)

# ── Drawdown ──────────────────────────────────────────
equity = (1 + df['Return'].fillna(0)).cumprod()
df['Equity'] = equity
peak = [Link]()
df['DD'] = (equity - peak) / peak * 100

# ── Cumulative return ─────────────────────────────────


df['CumRet'] = (equity - 1) * 100

© 2025 Quant Projects Guide 33


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

[Link] = [Link](subset=['Vol_20','Sharpe_21'])

def plot(self):
df = [Link]

fig = make_subplots(
rows=4, cols=2,
shared_xaxes=False,
vertical_spacing=0.08,
horizontal_spacing=0.10,
subplot_titles=[
f'{[Link]} Price',
'Return Distribution',
'Rolling Volatility (%)',
'Rolling Sharpe Ratio',
'Drawdown (%)',
'Cumulative Return (%)',
'Volume',
'Volatility Regime (20d vs 252d)',
],
specs=[
[{'type':'xy'}, {'type':'xy'}],
[{'type':'xy'}, {'type':'xy'}],
[{'type':'xy'}, {'type':'xy'}],
[{'type':'xy'}, {'type':'xy'}],
],
)

# ── (1,1) Price ───────────────────────────────────────


fig.add_trace([Link](
x=[Link], y=df['Close'],
line=dict(color='#74b9ff',width=1.5), name='Close'), row=1, col=1)

# ── (1,2) Return Distribution ─────────────────────────


rets = df['Return'].dropna() * 100
fig.add_trace([Link](
x=rets, nbinsx=80,
marker_color='#a29bfe',
opacity=0.75, name='Returns'), row=1, col=2)
# Normal distribution overlay
x_norm = [Link]([Link](), [Link](), 200)
norm_y = (1/([Link]()*[Link](2*[Link]))) * [Link](-0.5*((x_norm-
[Link]())/[Link]())**2)
norm_y *= len(rets) * ([Link]()-[Link]()) / 80
fig.add_trace([Link](
x=x_norm, y=norm_y,
line=dict(color='#fdcb6e',width=2), name='Normal Fit'), row=1, col=2)

# ── (2,1) Rolling Volatility ──────────────────────────


fig.add_trace([Link](
x=[Link], y=df['Vol_20'],
line=dict(color='#fd79a8',width=1.5), name='Vol 20d'), row=2, col=1)
fig.add_trace([Link](
x=[Link], y=df['Vol_60'],
line=dict(color='#fdcb6e',width=1.5), name='Vol 60d'), row=2, col=1)
fig.add_trace([Link](
x=[Link], y=df['Vol_252'],

© 2025 Quant Projects Guide 34


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

line=dict(color='#a29bfe',width=2,dash='dash'), name='Vol 252d'),


row=2, col=1)

# ── (2,2) Rolling Sharpe ──────────────────────────────


for col_name, color, name in [('Sharpe_21','#fd79a8','Sharpe 1mo'),
('Sharpe_63','#74b9ff','Sharpe 3mo'),
('Sharpe_252','#00b894','Sharpe 1yr')]:
fig.add_trace([Link](
x=[Link], y=df[col_name],
line=dict(color=color,width=1.5), name=name), row=2, col=2)
fig.add_hline(y=0, line_dash='dash', line_color='white', row=2, col=2)
fig.add_hline(y=1, line_dash='dot', line_color='#fdcb6e', row=2, col=2)
fig.add_hline(y=2, line_dash='dot', line_color='#00b894', row=2, col=2)

# ── (3,1) Drawdown ────────────────────────────────────


fig.add_trace([Link](
x=[Link], y=df['DD'],
line=dict(color='#e17055',width=1.5),
fill='tozeroy', fillcolor='rgba(225,112,85,0.15)',
name='Drawdown'), row=3, col=1)

# ── (3,2) Cumulative Return ───────────────────────────


fig.add_trace([Link](
x=[Link], y=df['CumRet'],
line=dict(color='#00b894',width=2),
fill='tozeroy', fillcolor='rgba(0,184,148,0.1)',
name='Cum. Return'), row=3, col=2)

# ── (4,1) Volume ──────────────────────────────────────


vol_colors = ['#00b894' if r>=0 else '#e17055' for r in
df['Return'].fillna(0)]
fig.add_trace([Link](
x=[Link], y=df['Volume'],
marker_color=vol_colors, name='Volume'), row=4, col=1)

# ── (4,2) Volatility regime ───────────────────────────


fig.add_trace([Link](
x=df['Vol_252'], y=df['Vol_20'],
mode='markers',
marker=dict(color=range(len(df)), colorscale='Viridis', size=4,
opacity=0.6),
name='Vol Regime'), row=4, col=2)
fig.add_shape(type='line', x0=0, x1=80, y0=0, y1=80,
line=dict(color='white',dash='dash'), row=4, col=2)

# ── Layout ───────────────────────────────────────────
fig.update_layout(
title=f'{[Link]} — Complete Analytics Dashboard',
template='plotly_dark',
height=1200,
showlegend=True,
)
[Link]()
return fig

# ── Run it ───────────────────────────────────────────────────
dash = StrategyDashboard(ticker='SPY', period='2y', interval='1d')
[Link]()

© 2025 Quant Projects Guide 35


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

# Try different tickers:


# StrategyDashboard('AAPL').plot()
# StrategyDashboard('QQQ', period='5y').plot()

© 2025 Quant Projects Guide 36


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

PROJEC
T5
Options Greeks Calculator & Interactive Pricer

Building a Black-Scholes Options Pricer from Scratch


This project builds a complete options pricing and Greeks calculator. You can input any option and
instantly see its price and all Greeks — then visualise how they change as the underlying moves.

import numpy as np
from [Link] import norm
import pandas as pd
import plotly.graph_objects as go
from [Link] import make_subplots

class BlackScholes:
'''
Complete Black-Scholes options pricer with all Greeks.

S = Current stock price


K = Strike price
T = Time to expiry in YEARS (e.g. 30 days = 30/365 = 0.0822)
r = Risk-free rate (annualised, e.g. 0.05 = 5%)
sigma = Implied volatility (annualised, e.g. 0.20 = 20%)
'''

def __init__(self, S, K, T, r, sigma, option_type='call'):


self.S = S
self.K = K
self.T = max(T, 1e-6) # Prevent division by zero at expiry
self.r = r
[Link]= sigma
[Link] = option_type.lower()

# ── d1 and d2: the core inputs to the formula ────────


self.d1 = ([Link](S/K) + (r + 0.5*sigma**2)*self.T) / (sigma *
[Link](self.T))
self.d2 = self.d1 - sigma * [Link](self.T)

def price(self) -> float:


'''Option price.'''
if [Link] == 'call':
return (self.S * [Link](self.d1) -
self.K * [Link](-self.r*self.T) * [Link](self.d2))
else: # put
return (self.K * [Link](-self.r*self.T) * [Link](-self.d2) -
self.S * [Link](-self.d1))

def delta(self) -> float:


'''Rate of change of price vs underlying.'''
if [Link] == 'call':
return [Link](self.d1)
else:

© 2025 Quant Projects Guide 37


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

return [Link](self.d1) - 1

def gamma(self) -> float:


'''Rate of change of delta vs underlying (same for call and put).'''
return [Link](self.d1) / (self.S * [Link] * [Link](self.T))

def theta(self) -> float:


'''Daily time decay (divide annual by 365).'''
term1 = -(self.S * [Link](self.d1) * [Link]) / (2 *
[Link](self.T))
if [Link] == 'call':
theta_annual = term1 - self.r * self.K * [Link](-self.r*self.T) *
[Link](self.d2)
else:
theta_annual = term1 + self.r * self.K * [Link](-self.r*self.T) *
[Link](-self.d2)
return theta_annual / 365 # Per day

def vega(self) -> float:


'''Change in price per 1% change in vol (divide by 100 for %).'''
return self.S * [Link](self.d1) * [Link](self.T) / 100

def rho(self) -> float:


'''Change in price per 1% change in interest rates.'''
if [Link] == 'call':
return self.K * self.T * [Link](-self.r*self.T) * [Link](self.d2) /
100
else:
return -self.K * self.T * [Link](-self.r*self.T) * [Link](-self.d2)
/ 100

def implied_vol_approx(self) -> float:


'''Brenner-Subrahmanyam approximation (useful estimate).'''
return ([Link]() / self.S) * [Link](2*[Link]/self.T)

def summary(self) -> dict:


return {
'Type' : [Link](),
'Price' : round([Link](), 4),
'Delta' : round([Link](), 4),
'Gamma' : round([Link](), 6),
'Theta' : round([Link](), 4),
'Vega' : round([Link](), 4),
'Rho' : round([Link](), 4),
'Intrinsic': round(max(self.S-self.K,0) if [Link]=='call' else
max(self.K-self.S,0), 4),
'Time Val': round([Link]()-max(self.S-self.K,0 if
[Link]=='call' else self.K-self.S), 4),
}

# ── Example: SPY ATM Call ─────────────────────────────────────


opt = BlackScholes(S=450, K=450, T=30/365, r=0.05, sigma=0.18,
option_type='call')
for k, v in [Link]().items():
print(f' {k:12s}: {v}')

# Output:
# --- Type : CALL
# --- Price : 8.2341

© 2025 Quant Projects Guide 38


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

# --- Delta : 0.5264


# --- Gamma : 0.0143
# --- Theta : -0.1897
# --- Vega : 0.7645
# --- Rho : 0.1823
# --- Intrinsic : 0.0
# --- Time Val : 8.2341

Visualise How Greeks Change With Stock Price


def plot_greeks_vs_price(K=450, T=30/365, r=0.05, sigma=0.18,
option_type='call'):
'''Show all Greeks across a range of stock prices.'''

stock_prices = [Link](K*0.80, K*1.20, 200) # 80% to 120% of strike

prices, deltas, gammas, thetas, vegas = [], [], [], [], []

for S in stock_prices:
opt = BlackScholes(S=S, K=K, T=T, r=r, sigma=sigma,
option_type=option_type)
[Link]([Link]())
[Link]([Link]())
[Link]([Link]())
[Link]([Link]())
[Link]([Link]())

fig = make_subplots(
rows=2, cols=3,
subplot_titles=['Option Price','Delta','Gamma','Theta (daily)','Vega (per
1% vol)','Payoff at Expiry'],
)

pairs = [
(prices, '#74b9ff', 'Price', 1, 1),
(deltas, '#00b894', 'Delta', 1, 2),
(gammas, '#fdcb6e', 'Gamma', 1, 3),
(thetas, '#e17055', 'Theta', 2, 1),
(vegas, '#a29bfe', 'Vega', 2, 2),
]
for y, color, name, row, col in pairs:
fig.add_trace([Link](
x=stock_prices, y=y,
line=dict(color=color, width=2),
name=name), row=row, col=col)
fig.add_vline(x=K, line_dash='dash', line_color='white', row=row,
col=col)

# Payoff at expiry
payoff = [max(S-K,0) for S in stock_prices] if option_type=='call' else
[max(K-S,0) for S in stock_prices]
# Subtract premium for P&L
premium = BlackScholes(S=K, K=K, T=T, r=r, sigma=sigma,
option_type=option_type).price()
pnl = [p - premium for p in payoff]

© 2025 Quant Projects Guide 39


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

fig.add_trace([Link](
x=stock_prices, y=pnl,
line=dict(color='#00b894', width=2),
fill='tozeroy', fillcolor='rgba(0,184,148,0.15)',
name='P&L at Expiry'), row=2, col=3)
fig.add_hline(y=0, line_dash='dash', line_color='white', row=2, col=3)
fig.add_vline(x=K, line_dash='dash', line_color='#fdcb6e', row=2, col=3)

fig.update_layout(
title=f'{option_type.upper()} Option Greeks — Strike {K}, {T*365:.0f}d,
IV {sigma*100:.0f}%',
template='plotly_dark', height=700,
)
[Link]()

plot_greeks_vs_price(K=450, T=30/365, sigma=0.18, option_type='call')


plot_greeks_vs_price(K=450, T=30/365, sigma=0.18, option_type='put')

© 2025 Quant Projects Guide 40


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

PROJEC
T6
Paper Trading Backend with Alpaca API

What Is Paper Trading?


Paper trading means running your strategy with real market data and real order execution logic —
but using fake money. It is the final test before risking real capital. Alpaca provides a free paper
trading API that connects directly to live market data.

Setting Up Your Alpaca Account


6. Go to [Link] and create a free account.
7. In the dashboard, click 'Paper Trading' in the top-left to switch to paper mode.
8. Click 'Generate New Key' under 'API Keys' — save your API Key and Secret Key
somewhere safe.
9. The paper trading base URL is: [Link]

Connecting to Alpaca in Python


import alpaca_trade_api as tradeapi
import pandas as pd

# ── Your API credentials (replace with your own) ──────────────


API_KEY = 'YOUR_API_KEY_HERE'
API_SECRET = 'YOUR_SECRET_KEY_HERE'
BASE_URL = '[Link] # Paper trading

# ── Connect ───────────────────────────────────────────────────
api = [Link](API_KEY, API_SECRET, BASE_URL, api_version='v2')

# ── Check account status ──────────────────────────────────────


account = api.get_account()
print(f'Account Status : {[Link]}')
print(f'Portfolio Value: ${float(account.portfolio_value):,.2f}')
print(f'Cash Available : ${float([Link]):,.2f}')
print(f'Buying Power : ${float(account.buying_power):,.2f}')

# Output:
# --- Account Status : ACTIVE
# --- Portfolio Value: $100,000.00
# --- Cash Available : $100,000.00
# --- Buying Power : $100,000.00

Getting Live Market Data from Alpaca


import alpaca_trade_api as tradeapi

© 2025 Quant Projects Guide 41


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

import pandas as pd

api = [Link](API_KEY, API_SECRET, BASE_URL, api_version='v2')

# ── Get historical bars ───────────────────────────────────────


bars = api.get_bars(
'SPY',
[Link], # 1-minute bars
start='2024-01-15',
end='2024-01-16',
adjustment='raw',
).df

print([Link]())

# ── Get 5-minute bars ─────────────────────────────────────────


bars_5m = api.get_bars(
'SPY',
[Link](5, [Link]),
start='2024-01-10',
end='2024-01-15',
).df

print(f'5-minute bars: {len(bars_5m)} rows')

# ── Get current quote (live bid/ask) ─────────────────────────


quote = api.get_latest_quote('SPY')
print(f'Bid: ${[Link]:.2f} x {[Link]}')
print(f'Ask: ${[Link]:.2f} x {quote.as_}')
print(f'Spread: ${[Link] - [Link]:.2f}')

Placing Orders
# ── Market order (executes immediately at best price) ─────────
def market_buy(ticker, qty):
order = api.submit_order(
symbol=ticker,
qty=qty,
side='buy',
type='market',
time_in_force='day', # Order expires at end of day if not filled
)
print(f'BUY {qty} {ticker} | Order ID: {[Link]} | Status: {[Link]}')
return order

def market_sell(ticker, qty):


order = api.submit_order(
symbol=ticker,
qty=qty,
side='sell',
type='market',
time_in_force='day',
)
print(f'SELL {qty} {ticker} | Order ID: {[Link]} | Status: {[Link]}')
return order

© 2025 Quant Projects Guide 42


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

# ── Limit order (only fills at your price or better) ──────────


def limit_buy(ticker, qty, limit_price):
order = api.submit_order(
symbol=ticker,
qty=qty,
side='buy',
type='limit',
limit_price=limit_price,
time_in_force='gtc', # Good Till Cancelled
)
return order

# ── Bracket order (entry + stop + target in one order) ────────


def bracket_buy(ticker, qty, take_profit, stop_loss):
'''A bracket order automatically manages your trade.'''
order = api.submit_order(
symbol=ticker,
qty=qty,
side='buy',
type='market',
time_in_force='day',
order_class='bracket',
take_profit={'limit_price': take_profit},
stop_loss={'stop_price': stop_loss},
)
print(f'BRACKET BUY {qty} {ticker} | TP: {take_profit} | SL: {stop_loss}')
return order

# ── Check existing positions ──────────────────────────────────


def get_positions():
positions = api.list_positions()
if not positions:
print('No open positions.')
return
for p in positions:
print(f'{[Link]:6s} | Qty: {[Link]:6s} | Avg: $
{float(p.avg_entry_price):.2f}'
f' | Market: ${float(p.market_value):8,.2f}'
f' | P&L: ${float(p.unrealized_pl):+,.2f}
({float(p.unrealized_plpc)*100:.2f}%)')

# ── Cancel all orders ────────────────────────────────────────


def cancel_all():
api.cancel_all_orders()
print('All orders cancelled.')

# ── Liquidate everything (emergency) ─────────────────────────


def close_all_positions():
api.close_all_positions()
print('All positions closed.')

The Automated Strategy Loop


import time

© 2025 Quant Projects Guide 43


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

import datetime
import yfinance as yf
import pandas as pd
import numpy as np

def is_market_open():
'''Check if US stock market is currently open.'''
clock = api.get_clock()
return clock.is_open

def get_latest_bars(ticker, n=50, interval='5m'):


'''Get the last n 5-minute bars for signal calculation.'''
df = [Link](ticker, period='5d', interval=interval, progress=False)
df = df.between_time('09:30','16:00').dropna()
return [Link][-n:]

def run_strategy_loop(ticker='SPY', qty=10, check_interval_seconds=300):


'''
Main strategy loop.
Runs every check_interval_seconds (default: every 5 minutes).
Checks the EMA9/EMA21 crossover signal and trades accordingly.
'''
print(f'Starting strategy on {ticker} | Checking every
{check_interval_seconds}s')
position_side = None # Track our current position: None, 'long', 'short'

while True:
try:
now = [Link]().strftime('%H:%M:%S')

if not is_market_open():
print(f'[{now}] Market closed. Waiting...')
[Link](60)
continue

# ── Get latest data ───────────────────────────────


df = get_latest_bars(ticker, n=50)

# ── Calculate signal ──────────────────────────────


ema9 = df['Close'].ewm(span=9, adjust=False).mean()
ema21 = df['Close'].ewm(span=21, adjust=False).mean()

fast_above_now = [Link][-1] > [Link][-1]


fast_above_prev = [Link][-2] > [Link][-2]

cross_up = fast_above_now and not fast_above_prev


cross_down = not fast_above_now and fast_above_prev

current_price = df['Close'].iloc[-1]
print(f'[{now}] {ticker}: ${current_price:.2f} | EMA9: {[Link][-
1]:.2f} | EMA21: {[Link][-1]:.2f}', end=' ')

# ── Execute trades ────────────────────────────────


if cross_up and position_side != 'long':
print('→ CROSS UP detected!')
if position_side == 'short':
# Close short first
market_buy(ticker, qty) # Buy to cover

© 2025 Quant Projects Guide 44


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

# Open long
market_buy(ticker, qty)
position_side = 'long'

elif cross_down and position_side != 'short':


print('→ CROSS DOWN detected!')
if position_side == 'long':
# Close long first
market_sell(ticker, qty)
# Open short
market_sell(ticker, qty)
position_side = 'short'

else:
print(f'→ No signal. Position: {position_side}')

# ── Wait for next check ───────────────────────────


[Link](check_interval_seconds)

except KeyboardInterrupt:
print('\nStopping strategy. Closing all positions...')
close_all_positions()
cancel_all()
break
except Exception as e:
print(f'Error: {e}')
[Link](30) # Wait 30s and retry on error

# ── Start it (PAPER TRADING ONLY) ────────────────────────────


# run_strategy_loop('SPY', qty=10)
# Press Ctrl+C to stop and close all positions

© 2025 Quant Projects Guide 45


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

PROJEC
T7
Portfolio Risk Manager with Live Position Monitoring

Real-Time Risk Monitoring System


This project builds a risk management layer that sits on top of any strategy. It monitors all positions
in real time and automatically enforces daily loss limits, position size limits, and stop-losses.

import alpaca_trade_api as tradeapi


import pandas as pd
import numpy as np
from datetime import datetime

class RiskManager:
'''
Monitors positions and enforces risk limits.
Should be run in parallel with your trading strategy.
'''

def __init__(self, api,


max_position_size = 5000, # Max $ per position
daily_loss_limit = -500, # Stop trading if P&L < this
max_drawdown_pct = 0.05, # Max drawdown as % of portfolio
position_stop_pct = 0.02, # Per-position stop loss (2%)
verbose=True):
[Link] = api
self.max_pos = max_position_size
[Link] = daily_loss_limit
self.max_dd = max_drawdown_pct
self.pos_stop = position_stop_pct
[Link] = verbose
self.trading_halted = False
self.start_equity = float(api.get_account().equity)

def log(self, msg):


if [Link]:
print(f'[RISK {[Link]().strftime("%H:%M:%S")}] {msg}')

def get_daily_pnl(self) -> float:


account = [Link].get_account()
return float([Link]) - float(account.last_equity)

def check_daily_loss_limit(self) -> bool:


'''Returns True if trading should be halted.'''
pnl = self.get_daily_pnl()
if pnl < [Link]:
[Link](f'DAILY LOSS LIMIT BREACHED: P&L = ${pnl:,.2f} < limit $
{[Link]:,.2f}')
return True
return False

def check_drawdown(self) -> bool:

© 2025 Quant Projects Guide 46


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

'''Returns True if max drawdown breached.'''


equity = float([Link].get_account().equity)
dd_pct = (equity - self.start_equity) / self.start_equity
if dd_pct < -self.max_dd:
[Link](f'MAX DRAWDOWN BREACHED: {dd_pct:.2%}')
return True
return False

def check_position_sizes(self):
'''Reduce any position that exceeds max size.'''
positions = [Link].list_positions()
for p in positions:
mkt_val = abs(float(p.market_value))
if mkt_val > self.max_pos:
excess_val = mkt_val - self.max_pos
excess_qty = int(excess_val / float(p.current_price))
if excess_qty > 0:
[Link](f'SIZE LIMIT: {[Link]} ${mkt_val:,.0f} > $
{self.max_pos:,.0f}. Reducing by {excess_qty}.')
side = 'sell' if float([Link]) > 0 else 'buy'
[Link].submit_order(
symbol=[Link], qty=excess_qty,
side=side, type='market', time_in_force='day'
)

def check_stop_losses(self):
'''Exit any position that has lost more than stop%.'''
positions = [Link].list_positions()
for p in positions:
pnl_pct = float(p.unrealized_plpc) # as decimal, e.g. -0.025 = -
2.5%
if pnl_pct < -self.pos_stop:
[Link](f'STOP LOSS: {[Link]} {pnl_pct:.2%}. Closing
position.')
[Link].close_position([Link])

def run_checks(self) -> bool:


'''
Run all risk checks. Returns False if trading should halt.
Call this every few minutes in your main loop.
'''
if self.trading_halted:
[Link]('Trading already halted.')
return False

# Order matters: check kill conditions first


if self.check_daily_loss_limit() or self.check_drawdown():
self.trading_halted = True
[Link]('HALTING TRADING — closing all positions.')
[Link].close_all_positions()
[Link].cancel_all_orders()
return False

# Non-halting checks
self.check_position_sizes()
self.check_stop_losses()

return True # OK to continue trading

© 2025 Quant Projects Guide 47


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

def status_report(self):
'''Print current risk status.'''
account = [Link].get_account()
daily_pnl= self.get_daily_pnl()
equity = float([Link])
dd_pct = (equity - self.start_equity) / self.start_equity * 100
positions= [Link].list_positions()

print('\n' + '='*50)
print(' RISK STATUS REPORT')
print('='*50)
print(f' Portfolio Value : ${equity:,.2f}')
print(f' Daily P&L : ${daily_pnl:+,.2f}')
print(f' Drawdown : {dd_pct:+.2f}%')
print(f' Daily P&L Limit : ${[Link]:,.2f}')
print(f' Max Drawdown : {-self.max_dd*100:.1f}%')
print(f' Trading Halted : {self.trading_halted}')
print(f' Open Positions : {len(positions)}')
for p in positions:
print(f' {[Link]}: {[Link]} @ ${float(p.avg_entry_price):.2f}',
f'| P&L: ${float(p.unrealized_pl):+,.2f}
({float(p.unrealized_plpc)*100:.2f}%)')
print('='*50)

# ── Usage ─────────────────────────────────────────────────────
# rm = RiskManager(api, max_position_size=5000, daily_loss_limit=-300)
# In your main loop:
# if not rm.run_checks():
# break # Stop trading, risk limits breached

© 2025 Quant Projects Guide 48


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

PROJEC
T8
Walk-Forward Optimiser — Avoiding Overfitting

The Most Important Project: Honest Strategy Testing


This is the project that separates real quant research from amateur curve-fitting. Walk-forward
testing is the only way to know if your strategy has genuine edge — it tests the strategy on data it
never saw during development.

import pandas as pd
import numpy as np
import yfinance as yf
from itertools import product
import plotly.graph_objects as go
from [Link] import make_subplots

class WalkForwardOptimiser:
'''
Walk-forward testing framework.

Process:
1. Split data into rolling windows
2. In each window: optimise parameters on first 70% (in-sample)
3. Test best parameters on last 30% (out-of-sample)
4. Concatenate all out-of-sample results = honest performance estimate
'''

def __init__(self, df, window_size=252, oos_size=63, step=63):


'''
df : Price DataFrame
window_size : Total window length in bars (252 = 1 year daily)
oos_size : Out-of-sample portion in bars (63 = 3 months)
step : Move forward this many bars each iteration
'''
[Link] = df
self.window_size = window_size
self.oos_size = oos_size
self.is_size = window_size - oos_size
[Link] = step
[Link] = []

def _run_single(self, df_is, df_oos, fast, slow):


'''Run EMA crossover strategy on a DataFrame. Return Sharpe.'''
for df in [df_is, df_oos]:
ema_fast = df['Close'].ewm(span=fast, adjust=False).mean()
ema_slow = df['Close'].ewm(span=slow, adjust=False).mean()
signal = [Link]([Link](ema_fast > ema_slow, 1, -1),
index=[Link])
ret = df['Close'].pct_change() * [Link](1)
ret = [Link]()
pass

© 2025 Quant Projects Guide 49


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

# Run on in-sample
ema_fast = df_is['Close'].ewm(span=fast, adjust=False).mean()
ema_slow = df_is['Close'].ewm(span=slow, adjust=False).mean()
sig_is = [Link]([Link](ema_fast > ema_slow, 1, -1),
index=df_is.index)
ret_is = (df_is['Close'].pct_change() * sig_is.shift(1)).dropna()
sharpe_is= ret_is.mean()/ret_is.std()*[Link](252) if ret_is.std()>0 else
0

# Run on out-of-sample
ema_fast = df_oos['Close'].ewm(span=fast, adjust=False).mean()
ema_slow = df_oos['Close'].ewm(span=slow, adjust=False).mean()
sig_oos = [Link]([Link](ema_fast > ema_slow, 1, -1),
index=df_oos.index)
ret_oos = (df_oos['Close'].pct_change() * sig_oos.shift(1)).dropna()
sharpe_oos= ret_oos.mean()/ret_oos.std()*[Link](252) if ret_oos.std()>0
else 0

return sharpe_is, sharpe_oos, ret_oos

def run(self, fast_range=range(5,25,5), slow_range=range(20,80,10)):


'''
Run full walk-forward optimisation.
For each window: find best (fast,slow) on IS, apply to OOS.',
'''
all_param_combos = [(f,s) for f,s in product(fast_range, slow_range) if f
< s]
all_oos_returns = []
window_summary = []

n_windows = (len([Link]) - self.window_size) // [Link] + 1


print(f'Running {n_windows} walk-forward windows...')

for i in range(0, len([Link]) - self.window_size + 1, [Link]):


window = [Link][i : i + self.window_size]
df_is = [Link][:self.is_size]
df_oos = [Link][self.is_size:]

if len(df_oos) < 20:


continue

# ── Optimise on in-sample ────────────────────────


best_sharpe_is = -[Link]
best_params = None

for fast, slow in all_param_combos:


try:
sh_is, _, _ = self._run_single(df_is, df_oos, fast, slow)
if sh_is > best_sharpe_is:
best_sharpe_is = sh_is
best_params = (fast, slow)
except:
pass

if best_params is None:
continue

# ── Apply best params to out-of-sample ───────────

© 2025 Quant Projects Guide 50


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

fast, slow = best_params


sh_is, sh_oos, ret_oos = self._run_single(df_is, df_oos, fast, slow)

all_oos_returns.append(ret_oos)
window_summary.append({
'window_start' : df_is.index[0],
'oos_start' : df_oos.index[0],
'oos_end' : df_oos.index[-1],
'best_fast' : fast,
'best_slow' : slow,
'sharpe_is' : round(sh_is, 3),
'sharpe_oos' : round(sh_oos, 3),
})
print(f' Window {len(window_summary)}: params=({fast},{slow}) | IS
Sharpe={sh_is:.2f} | OOS Sharpe={sh_oos:.2f}')

# ── Concatenate OOS returns ───────────────────────────


if all_oos_returns:
combined_oos = [Link](all_oos_returns).sort_index()
combined_oos = combined_oos[~combined_oos.[Link]()]
else:
combined_oos = [Link](dtype=float)

self.summary_df = [Link](window_summary)
self.combined_oos= combined_oos

self._print_summary()
return self

def _print_summary(self):
oos = self.combined_oos.dropna()
if len(oos) == 0:
print('No OOS data.'); return

ann_ret = [Link]() * 252


ann_vol = [Link]() * [Link](252)
sharpe = ann_ret / ann_vol if ann_vol > 0 else 0
equity = (1 + oos).cumprod()
max_dd = ((equity - [Link]()) / [Link]()).min()

avg_is_sharpe = self.summary_df['sharpe_is'].mean()
avg_oos_sharpe = self.summary_df['sharpe_oos'].mean()
degrade_pct = (avg_is_sharpe - avg_oos_sharpe) / abs(avg_is_sharpe) *
100 if avg_is_sharpe != 0 else 0

print('\n' + '='*55)
print(' WALK-FORWARD RESULTS')
print('='*55)
print(f' Windows Tested : {len(self.summary_df)}')
print(f' Avg IS Sharpe : {avg_is_sharpe:.3f}')
print(f' Avg OOS Sharpe : {avg_oos_sharpe:.3f}')
print(f' Performance Decay : {degrade_pct:.1f}% (< 50% is acceptable)')
print(f' Combined OOS Ann. Return : {ann_ret:.2%}')
print(f' Combined OOS Ann. Vol : {ann_vol:.2%}')
print(f' Combined OOS Sharpe : {sharpe:.3f}')
print(f' Combined OOS Max DD : {max_dd:.2%}')
print('='*55)
print()

© 2025 Quant Projects Guide 51


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

print(' INTERPRETATION:')
if avg_oos_sharpe > 0.5:
print(' ✓ OOS Sharpe > 0.5: REAL EDGE detected in out-of-sample.')
else:
print(' ✗ OOS Sharpe < 0.5: Likely overfitting — edge not
confirmed.')
if degrade_pct < 50:
print(' ✓ Performance decay < 50%: Parameters are stable.')
else:
print(' ✗ Performance decay > 50%: Strategy is overfit to in-
sample.')
print('='*55)

# ── Run the walk-forward test ─────────────────────────────────


df = [Link]('SPY', start='2015-01-01', end='2024-01-01', progress=False)

wfo = WalkForwardOptimiser(df, window_size=252, oos_size=63, step=63)


[Link](fast_range=range(5, 30, 5), slow_range=range(25, 80, 10))

# View the summary table


print(wfo.summary_df.to_string())

© 2025 Quant Projects Guide 52


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

CHAPTE
R9
Putting It All Together — Your Complete System

The Full Architecture of an Automated Trading System


Now that you have built each component individually, here is how they connect into a single,
production-quality automated trading system. This is the architecture used in professional trading
firms, scaled down for an individual trader.

Layer Component Project What It Does

Data Market Data Fetcher Ch.1 Downloads 5-min OHLCV from yfinance
or Alpaca API continuously
Signal Indicator Engine Ch.3 Computes EMAs, RSI, MACD, ATR,
Bollinger Bands on incoming data
Signal Strategy Logic Project 1 & 2 Detects FVGs, crossovers, or any custom
condition — outputs +1/-1/0 signal
Risk Risk Manager Project 7 Checks position limits, daily P&L limit,
max drawdown before each trade
Execution Order Manager Project 6 Converts signal into Alpaca bracket order
(entry + TP + SL in one)
Analytics Backtester Project 3 Tests strategy historically before
deploying live
Analytics Walk-Forward Project 8 Validates the strategy was not overfit to
historical data
Monitoring Dashboard Project 4 & 5 Interactive Sharpe/Vol/Greeks charts
updated from live data

# ── Complete system template ──────────────────────────────────


import time, datetime
import yfinance as yf
import pandas as pd
import alpaca_trade_api as tradeapi

# 1. Config
TICKER = 'SPY'
QTY = 10
API_KEY = 'YOUR_KEY'
API_SECRET = 'YOUR_SECRET'
BASE_URL = '[Link]

# 2. Initialise
api = [Link](API_KEY, API_SECRET, BASE_URL, api_version='v2')

# 3. Main loop
def main():

© 2025 Quant Projects Guide 53


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

print(f'System starting. Trading {TICKER}.')


rm = RiskManager(api, max_position_size=5000, daily_loss_limit=-300)
position = None

while True:
if not is_market_open():
[Link](60); continue

# A) Risk check first — always


if not rm.run_checks():
print('Risk limits hit. Stopping.'); break

# B) Get data
df = [Link](TICKER, period='5d', interval='5m', progress=False)
df = df.between_time('09:30','16:00').dropna()

# C) Generate signals (replace with your strategy)


ema9 = df['Close'].ewm(span=9, adjust=False).mean()
ema21 = df['Close'].ewm(span=21, adjust=False).mean()
cross_up = ([Link][-1] > [Link][-1]) and ([Link][-2] <=
[Link][-2])
cross_down = ([Link][-1] < [Link][-1]) and ([Link][-2] >=
[Link][-2])

# D) Execute
atr = df['ATR'].iloc[-1] if 'ATR' in [Link] else df['Close'].iloc[-
1] * 0.005
price = df['Close'].iloc[-1]

if cross_up and position != 'long':


if position == 'short': market_buy(TICKER, QTY)
bracket_buy(TICKER, QTY,
take_profit=round(price + 2*atr, 2),
stop_loss =round(price - 1*atr, 2))
position = 'long'

elif cross_down and position != 'short':


if position == 'long': market_sell(TICKER, QTY)
position = 'short'

[Link](300) # Check every 5 minutes

if __name__ == '__main__':
main()

Your GitHub Portfolio Structure


Every project you build should be on GitHub with clean, documented code. Here is the
recommended folder structure:

quant_trader/
├── [Link] # Overview of all projects
├── [Link] # pip install -r [Link]

© 2025 Quant Projects Guide 54


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems


├── 01_data/
│ ├── data_fetcher.py # Multi-timeframe data download functions
│ └── data_cleaner.py # Handle missing data, splits, dividends

├── 02_indicators/
│ ├── moving_averages.py # SMA, EMA, VWAP
│ ├── [Link] # RSI, MACD, Stochastic
│ └── [Link] # ATR, Bollinger, Keltner

├── 03_strategies/
│ ├── fvg_strategy.py # Fair Value Gap detector + trader
│ ├── ema_crossover.py # 5-min crossover strategy
│ └── base_strategy.py # Abstract base class for all strategies

├── 04_backtesting/
│ ├── [Link] # Backtester class with all metrics
│ ├── walk_forward.py # Walk-forward optimiser
│ └── [Link] # Sharpe, drawdown, all stats functions

├── 05_execution/
│ ├── alpaca_broker.py # Order management via Alpaca API
│ ├── risk_manager.py # Position & portfolio risk controls
│ └── main_loop.py # Main automated trading loop

├── 06_analytics/
│ ├── strategy_dashboard.py # Sharpe/Vol interactive dashboard
│ ├── monthly_heatmap.py # Monthly returns visualisation
│ └── options_pricer.py # Black-Scholes + Greeks

└── notebooks/
├── 01_exploration.ipynb # Data exploration notebooks
├── 02_signal_research.ipynb
└── 03_backtest_results.ipynb

What to Put on Your Jane Street Application


When you complete all 8 projects, here is exactly what to write about them:

Application Field What to Write

Project Title Automated Quant Trading System: FVG & EMA Crossover Strategies
Technical Skills Python, pandas, NumPy, Plotly, Black-Scholes, Alpaca API, walk-
Demonstrated forward testing
Quantitative Skills Sharpe ratio optimisation, drawdown analysis, walk-forward validation,
Demonstrated Kelly criterion position sizing
Key Results to Mention Backtest Sharpe ratio (cite the number), max drawdown, % of FVGs
that fill, OOS vs IS Sharpe comparison
GitHub Link Link directly to your quant_trader repository — make sure README is
clear and code is documented
What It Proves You can: write quantitative trading code, backtest honestly, manage

© 2025 Quant Projects Guide 55


QUANT TRADER: Python Projects Bible From Zero to Automated Trading Systems

risk programmatically, and execute automated orders — all core quant


trader skills

Checklist: Am I Ready to Apply?

Skill How to Verify

FVG Detection Can you explain the three-candle pattern and show code that
detects it correctly?
Backtest Realism Does your backtest use next-bar execution? Does it account for
transaction costs? Do you have a walk-forward result?
Interactive Charts Can you load data and produce a candlestick chart with indicators
and signals in under 10 minutes?
Sharpe Dashboard Can you load any ticker and see rolling Sharpe, volatility, and
drawdown instantly?
Alpaca Integration Can you place a bracket order in paper trading with a stop and
target attached?
Risk Management Does your strategy automatically stop trading if the daily loss limit
is hit?
Options Greeks Can you calculate the delta, gamma, theta, and vega of any option
and explain what each number means?
Walk-Forward Test Have you compared your strategy's in-sample vs out-of-sample
Sharpe ratio and can you explain what the difference tells you?

────────────────────────────────────────
The code you write is the proof you understand.
Build every project. Push every commit. Show your work.
The GitHub repository is your trading floor.

© 2025 Quant Projects Guide 56

You might also like