6.2.8 · HinglishBacktesting Frameworks
Learn Python tools (pandas, backtrader, zipline)
6.2.8· Stock-Market › Backtesting Frameworks
The Foundation: pandas for Market Data
pandas pehle kyun? Kyunki saari strategies sirf OHLCV data ke transformations hain. Aapko karna hoga:
- CSVs/databases ko memory mein load karna
- Indicators calculate karna (SMA = Close ka rolling mean)
- Signals generate karna (buy karo jab price > SMA ho)
- Multiple stocks ko ek hi timeline par align karna
Core pandas Operations for Trading
Operation 1: Loading and Indexing
import pandas as pd
# Load stock data
df = pd.read_csv('AAPL.csv', parse_dates=['Date'], index_col='Date')
# Why parse_dates? Makes 'Date' a datetime object, unlocking time operations
# Why index_col? Puts Date as the row index, so df.loc['2024-01-15'] works
print(df.head())
# Open High Low Close Volume
# Date
# 2024-01-02 185.0 186.5 184.0 185.5 5000000Yeh kyun matter karta hai: DatetimeIndex se aap date ranges ke basis par slice kar sakte ho (df['2024-01':'2024-03']), alag frequencies mein resample kar sakte ho, aur time-based joins kar sakte ho.
Operation 2: Indicators Calculate Karna
# Simple Moving Average (20-day)
df['SMA_20'] = df['Close'].rolling(window=20).mean()
# Why rolling()? It creates a moving window that slides over your data.
# .mean() calculates the average of those20 values at each step.
# Exponential Moving Average (gives more weight to recent prices)
df['EMA_12'] = df['Close'].ewm(span=12, adjust=False).mean()
# Why adjust=False? Matches industry-standard EMA calculation.Rolling Mean ka Derivation: jahan = time par price, = window size.
Day 100 par 20-day SMA ke liye:
The Framework: backtrader for Strategy Logic
Sirf pandas kyun nahi? Aap pandas mein returns compute kar sakte ho, lekin manually karna padega:
- Time ke saath cash aur positions track karna
- Partial fills, slippage, commissions handle karna
- Careful indexing se lookahead avoid karna
- Portfolio metrics compute karna
backtrader yeh sab karta hai, taaki aap strategy logic par focus kar sako.
Anatomy of a backtrader Strategy
import backtrader as bt
class SmaCrossover(bt.Strategy):
params = (('sma_short', 20), ('sma_long', 50)) # Configurable parameters
def __init__(self):
# Indicators are calculated ONCE on all data, but accessed bar-by-bar
self.sma_short = bt.indicators.SMA(self.data.close, period=self.params.sma_short)
self.sma_long = bt.indicators.SMA(self.data.close, period=self.params.sma_long)
self.crossover = bt.indicators.CrossOver(self.sma_short, self.sma_long)
def next(self):
# Called for each bar (after SMA warmup period)
if not self.position: # No position: look for entry
if self.crossover > 0: # Short MA crossed above Long MA
self.buy(size=100) # Buy 100 shares
else: # Have position: look for exit
if self.crossover < 0: # Short MA crossed below Long MA
self.close() # Close position__init__ aur next alag kyun?
__init__: Indicators define karo. backtrader inhe SAARE data par pehle se compute kar leta hai (efficient).next: Trading logic. Sequentially call hota hai, sirf current + past bars dekh sakta hai (lookahead prevent karta hai).
CrossOver Kaise Kaam Karta Hai:
+1 & \text{if } \text{SMA}_{\text{short},t} > \text{SMA}_{\text{long},t} \text{ and } \text{SMA}_{\text{short},t-1} \leq \text{SMA}_{\text{long},t-1} \\ -1 & \text{if } \text{SMA}_{\text{short},t} < \text{SMA}_{\text{long},t} \text{ and } \text{SMA}_{\text{short},t-1} \geq \text{SMA}_{\text{long},t-1} \\ 0 & \text{otherwise} \end{cases}$$ > [!example] Backtest Chalana > ```python > cerebro = bt.Cerebro() # The engine > cerebro.addstrategy(SmaCrossover) > # Load data > data = bt.feeds.PandasData(dataname=df) # df is your pandas DataFrame > cerebro.adddata(data) > > # Set initial capital and commission > cerebro.broker.setcash(100000.0) # $100k starting capital > cerebro.broker.setcommission(commission=0.001) # 0.1% per trade > > print(f'Starting Portfolio Value: {cerebro.broker.getvalue():.2f}') > cerebro.run() > print(f'Final Portfolio Value: {cerebro.broker.getvalue():.2f}') > cerebro.plot() # Visualize equity curve and trades > ``` > > **`Cerebro` kyun?** Spanish mein "brain" hota hai—yeh sab kuch coordinate karta hai: data feeds, strategy, broker simulation, analyzers. > > **Output:** > ``` > Starting Portfolio Value: 100000.00 > Final Portfolio Value: 125430.50 > ``` > Plot mein price SMA overlays ke saath, buy/sell markers, aur time ke saath portfolio value dikhti hai. > [!formula] Portfolio Value Update > Har bar par, backtrader update karta hai: > $$V_t = \text{Cash}_t + \sum_{i} Q_{i,t} \cdot P_{i,t}$$ > jahan $V_t$ = portfolio value, $Q_{i,t}$ = asset $i$ ki held quantity, $P_{i,t}$ = asset $i$ ki price. > > Jab aap `buy(size=100)` karte ho price $P$ par: > - $\text{Cash}_t \leftarrow \text{Cash}_{t-1} - 100 \cdot P \cdot (1 + c)$ jahan $c$ = commission rate > - $Q_t \leftarrow Q_{t-1} + 100$ ### backtrader Analyzers ```python cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe') cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown') results = cerebro.run() strat = results[0] print(f"Sharpe Ratio: {strat.analyzers.sharpe.get_analysis()['sharperatio']:.2f}") print(f"Max Drawdown: {strat.analyzers.drawdown.get_analysis()['max']['drawdown']:.2f}%") ``` **Sharpe Ratio ka Derivation:** $$\text{Sharpe} = \frac{\bar{R} - R_f}{\sigma_R}$$ jahan $\bar{R}$ = mean return period, $R_f$ = risk-free rate (aksar 0), $\sigma_R$ = returns ka std dev. Agar daily returns $[0.01, -0.005, 0.02, \ldots]$ hain: 1. $\bar{R} = \text{mean}([0.01, -0.005, 0.02, \ldots])$ 2. $\sigma_R = \text{std}([0.01, -0.005, 0.02, \ldots])$ 3. Annualize karo: $\sqrt{252}$ se multiply karo (trading days/year) **Drawdown:** Maximum peak-to-trough decline. $$\text{DD}_t = \frac{V_t - \max_{s \leq t} V_s}{\max_{s \leq t} V_s}$$ --- ## The Industrial Upgrade: zipline > [!definition] zipline ka Purpose > ==zipline== Quantopian ka open-source engine hai, jo **realistic backtesting** at scale ke liye design kiya gaya hai. backtrader se key differences: > - **Pipeline API:** Hazaron stocks mein simultaneously factors (indicators) efficiently compute karta hai. > - **Built-in data bundles:** Quandl, Yahoo Finance (though Quantopian ka data defunct ho gaya, aap custom ingest kar sakte ho). > - **Minute-level granularity:** Intraday bars handle karta hai. > - **Realistic constraints:** Slippage models, market impact, shorting costs. **zipline kab use karein?** Jab aap "ek stock backtest" se "poore market ko daily scan karo aur 500 stocks ko momentum se rank karo" par shift ho rahe ho. ### zipline Strategy Structure ```python from zipline.api import order_target_percent, record, symbol def initialize(context): # Called once at start context.stock = symbol('AAPL') context.sma_window = 20 def handle_data(context, data): # Called every bar (daily or minute) hist = data.history(context.stock, 'close', context.sma_window + 1, '1d') current_price = data.current(context.stock, 'price') sma = hist.mean() if current_price > sma: order_target_percent(context.stock, 1.0) # Allocate 100% to AAPL elif current_price < sma: order_target_percent(context.stock, 0.0) # Exit position record(price=current_price, sma=sma) # Log for plotting ``` **`order_target_percent` kyun?** "100 shares khareedo" kehne ki jagah, aap kehte ho "Mujhe is stock mein apna 50% portfolio chahiye". zipline current holdings aur cash account karke us allocation tak pahunchne ke liye zaruri order calculate karta hai. > [!example] Multi-Stock Ranking ke liye zipline Pipeline > **Task:** Har din, saare S&P 500 stocks ko 20-day momentum se rank karo, top 10 mein long jao. > > ```python > from zipline.pipeline import Pipeline > from zipline.pipeline.factors import SimpleMovingAverage, Returns > from zipline.pipeline.data import USEquityPricing > def make_pipeline(): > momentum = Returns(window_length=20) # 20-day return > top_10 = momentum.top(10) # Boolean: True for top 10 stocks > > return Pipeline( > columns={'momentum': momentum}, > screen=top_10 # Only pass top 10 to handle_data > ) > > def initialize(context): > attach_pipeline(make_pipeline(), 'ranking') > > def before_trading_start(context, data): > context.output = pipeline_output('ranking') > context.longs = context.output.index # List of top 10 symbols > > def handle_data(context, data): > # Allocate 10% to each of the 10 stocks > for stock in context.longs: > order_target_percent(stock, 0.10) > > # Close any positions not in top 10 > for stock in context.portfolio.positions: > if stock not in context.longs: > order_target_percent(stock, 0.0) > ``` > > **Pipeline kyun?** 500 stocks ka momentum individually `handle_data` mein compute karna slow hoga. Pipeline ek vectorized pass mein saare stocks mein yeh karta hai, sirf filtered results (top 10) return karta hai. **Returns Factor Kaise Kaam Karta Hai:** $$R_{t,n} = \frac{P_t}{P_{t-n}} - 1$$ 20-day return ke liye: $R_{t,20} = \frac{P_t}{P_{t-20}} - 1$ Pipeline yeh saare stocks ke liye NumPy use karke parallel mein compute karta hai, phir rank aur filter karta hai. > [!mistake] Time Frequencies Mix Karna > **Galat Code:** > ```python > def handle_data(context, data): > hist = data.history(context.stock, 'close', 50, '1d') # Daily bars > if len(hist) < 50: > return # Not enough data > current_minuteprice = data.current(context.stock, 'price') # Minute bar! > ``` > **Yeh sahi kyun lagta hai:** "Mujhe daily SMA chahiye lekin precision ke liye minute bars par trade karna hai." > > **Problem:** Minute-frequency backtest mein `data.history(..., '1d')` minute bars ke last 50 **din** return karta hai (50 daily closes nahi). Aapka SMA minute data par compute ho raha hai, daily closes par nahi—bilkul alag signal hai. > > **Steel-man:** "Lekin maine '1d' frequency specify ki!" zipline mein, `history` mein frequency parameter ka matlab hai "is frequency par aggregate karo". Agar aapka backtest minute-by-minute chalta hai, toh aap har minute `handle_data` call kar rahe ho. Daily logic ke liye explicitly resample karo ya `before_trading_start` use karo (din mein ek baar call hota hai). > > **Fix:** > ```python > def before_trading_start(context, data): > # Runs once per day, gets clean daily bars > hist = data.history(context.stock, 'close', 50, '1d') > context.sma = hist.mean() > def handle_data(context, data): > # Runs every minute, uses pre-calculated daily SMA > if data.current(context.stock, 'price') > context.sma: > order_target_percent(context.stock, 1.0) > ``` --- ## Comparison: Har Tool Kab Use Karein | Tool | Best For | Pros | Cons | |------|----------|------| | **pandas** | Data cleaning, indicator calculation, quick analysis | Fast, flexible, full control | Built-in backtesting nahi, lookahead introduce karna aasan | | **backtrader** | Single-stock strategies, custom order types, detailed inspection | Seekhna aasan, great plotting, active community | Large universes (100+ stocks) par slower | | **zipline** | Multi-stock factor models, institutional realism, production pipelines | Pipeline API fast hai, realistic execution model | Seekhna mushkil, data ingestion setup chahiye | **Workflow:** pandas mein prototype karo → backtrader mein validate karo → zipline mein scale karo. --- > [!recall]- Ek 12-Saal-Ke Bachche Ko Samjhao > Socho tum ek nimbu pani ki dukaan chala rahe ho, aur tumhe figure out karna hai ki nimbu kharidne ka best time kab hai (jab saste ho) aur nimbu pani bechne ka best time kab hai (jab garmi ho aur log zyada pay karein). > > **pandas** woh notebook hai jisme tum har din nimbu ke daam likhte ho aur calculate karte ho jaise "Last 2 hafte ka average daam kya tha?" Yeh data organize karne ke liye hai. > > **backtrader** ek practice game jaisa hai jisme tum fake paise se apni nimbu pani ki dukaan chalane ka natak karte ho. Tum rules set karte ho: "Agar nimbu $2/bag se kam ho, toh 10 bags khareedo. Agar 90°F se zyada garm ho, toh $2 ki jagah $3/cup mein becho." Yeh game ek saal ke mausam aur nimbu ke daam se guzarta hai, dikhata hai kitne paise banate. Yeh score automatically rakhta hai. > > **zipline** wahi game hai lekin poore shehar mein 100 nimbu pani ki dukaaon ke liye. Sirf apni dukaan ki jagah, tum saare mohalle rank karte ho "Kisme sabse zyada parks hain?" (zyada customers) aur "Kisme nimbu saste hain?" phir apni 10 dukaanein top mohalle mein lagate ho. Yeh ek saath saari dukaaon manage karne ka complicated math handle karta hai. > > Key: **pandas** = tumhara calculator, **backtrader** = ek dukaan ke liye practice game, **zipline** = poore nimbu pani business ke liye practice game. --- > [!mnemonic] PBZ Stack > **P**andas: **P**repare data karo (clean, calculate) > **B**acktrader: **B**uild strategy karo (test logic, one stock) > **Z**ipline: **Z**oom out karo (bahut stocks tak scale karo, realistic) > > Ya socho "Pizza BeforeZz": Pehle dough prepare karo (pandas), pizza banao (backtrader), phir 100 pizzas deliver karo (zipline) sone se pehle. --- ## Connections - [[6.2.07-Data-sourcesand-cleaning|6.2.07-Data-sourcesand-cleaning]] – pandas woh CSVs load karta hai jo tumne yahan clean kiye - [[6.2.09-Walk-forward-analysis|6.2.09-Walk-forward-analysis]] – backtrader ka `cerebro.optstrategy()` parameters test karta hai; zipline rolling windows karta hai - [[6.3.01-Risk-adjusted-returns|6.3.01-Risk-adjusted-returns]] – Dono engines analyzers se Sharpe calculate karte hain - [[Technical-Indicators|Technical Indicators]] – Saare SMAs, RSI, Bollinger Bands jo tum pandas/backtrader built-ins se compute karte ho - [[Order-Types|Order Types]] – backtrader ka `buy()`, `sell()`, zipline mein `order_target_percent()` - [[Slippage-and-Transaction-Costs|Slippage and Transaction Costs]] – zipline ise realistically model karta hai; backtrader `broker.setcommission()` use karta hai --- ## #flashcards/stock-market Backtesting mein pandas ka kya purpose hai? :: pandas tabular data manipulation handle karta hai—OHLCV data load karna, indicators calculate karna (rolling means, EMA), aur signals generate karna. Yeh backtesting engine mein feed karne se pehle ki data preparation layer hai. `.rolling(window=20).mean()` manually sum karne ki jagah kyun use karein? ::: `.rolling()` ek sliding window banata hai jo automatically edge cases handle karta hai (shuru mein kafi data nahi), window ko sahi se shift karta hai (no lookahead), aur vectorized hai (large datasets par fast). Manual sum karne mein off-by-one errors aur lookahead bias ka risk hai. Lookahead bias kya hai aur pandas mein ise kaise prevent karein? ::: Lookahead bias hai past decisions ke liye future data use karna (jaise `.shift(-1)` se aaj ki trade decide karne ke liye kal ka return use karna). Ise prevent karo sirf `.shift(1)` ya positive shifts (peeche dekhna) use karke aur verify karke ki signal generation sirf current/past bars access kare. backtrader ka `next()` method kya karta hai? ::: `next()` har data bar par ek baar call hota hai (indicators warm up hone ke baad). Isme aapki trading logic hoti hai—conditions check karna aur orders place karna. Yeh sirf current bar aur pehle ke bars access kar sakta hai, lookahead bias prevent karta hai. backtrader ka `CrossOver` indicator kaise kaam karta hai? ::: `CrossOver(seriesA, seriesB)` +1 return karta hai jab seriesA, seriesB ke upar cross kare, -1 jab neeche cross kare, aur 0 otherwise. Yeh crossing ke moment ko detect karne ke liye current aur previous values compare karta hai. backtrader mein portfolio value ka formula kya hai? ::: $V_t = \text{Cash}_t + \sum_{i} Q_{i,t} \cdot P_{i,t}$ jahan $Q_{i,t}$ asset $i$ ki held quantity hai aur $P_{i,t}$ uski price hai. Jab aap buy karte ho, cash price × quantity × (1 + commission) se ghat jaata hai; held quantity badh jaati hai. zipline mein `order_target_percent(stock, 0.5)` kya karta hai? ::: Yeh orders place karta hai taaki diya gaya stock aapki portfolio value ka 50% bane. Agar aap currently 20% hold karte ho, toh aur kharidte ho. Agar 70% hold karte ho, toh kuch bechte ho. Yeh ek rebalancing function hai, fixed-size order nahi. zipline ka Pipeline API stocks par loop karne se zyada fast kyun hai? ::: Pipeline ek pass mein saare stocks mein simultaneously factors (indicators) compute karne ke liye vectorized NumPy operations use karta hai. Loop karna `data.history()` ko hazaron baar call karta hai, har baar overhead ke saath. Vectorization 10-100x faster hoti hai. Sharpe Ratio ka formula kya hai? ::: $\text{Sharpe} = \frac{\bar{R} - R_f}{\sigma_R}$ jahan $\bar{R}$ mean return hai, $R_f$ risk-free rate hai (aksar 0), aur $\sigma_R$ returns ka standard deviation hai. Yeh risk ki har unit par return measure karta hai. 20-day momentum kaise calculate karte hain? ::: $R_{t,20} = \frac{P_t}{P_{t-20}} - 1$ jahan $P_t$ aaj ki price hai aur $P_{t-20}$ 20 din pehle ki price hai. Yeh us period par percentage return hai. Drawdown kya hai? ::: Drawdown portfolio ki peak value se subsequent trough tak ki decline hai, percentage mein: $\text{DD}_t = \frac{V_t - \max_{s \leq t} V_s}{\max_{s \leq t} V_s}$. Max drawdown poore backtest period mein sabse buri aisi decline hai. backtrader vs zipline kab use karein? ::: backtrader single-stock ya small-universe strategies ke liye use karo jahan detailed inspection aur easy plotting chahiye. zipline multi-stock factor models ke liye, 100+ stocks rank karne ke liye, ya jab scale par realistic execution modeling (slippage, market impact) chahiye tab use karo. zipline mein time frequencies mix karne ka kya danger hai? ::: Agar aapka backtest minute-by-minute chalta hai lekin aap `handle_data` ke andar `data.history(..., '1d')` call karte ho, toh aapko 1 din worth ke minute bars milte hain (daily closes nahi), aapka indicator corrupt ho jaata hai. Daily logic ke liye `before_trading_start` use karo ya explicitly resample karo. backtrader mein indicators `next()` ki jagah `__init__` mein kyun declare karein? ::: Indicators `__init__` mein saare historical data par ek baar calculate hote hain (efficient). `next()` har bar call hota hai aur sirf indicator ki current value access karta hai (`self.sma[0]` jaisi indexing se). `next()` mein recalculate karna slow aur error-prone hoga. `cerebro.broker.setcommission(0.001)` kya karta hai? ::: Har trade par 0.1% commission set karta hai. Jab aap $50 par 100 shares khareedte ho, toh aap $5000 + 0.001×$5000 = $5005 pay karte ho. Yeh real trading costs model karta hai, backtest profitability ko realistic levels par laata hai. ## 🖼️ Concept Map ```mermaid flowchart TD Q[Does strategy work?] -->|solved by| STACK[Trading Stack] STACK -->|foundation| PD[pandas] STACK -->|framework| BT[backtrader] STACK -->|institutional upgrade| ZP[zipline] PD -->|stores| OHLCV[OHLCV tabular data] OHLCV -->|indexed by| DTI[DatetimeIndex] DTI -->|enables| SLICE[Date slicing and resampling] PD -->|rolling mean| SMA[SMA indicator] PD -->|ewm span| EMA[EMA indicator] SMA -->|price crosses above| SIG[Buy/Sell Signal] SIG -->|diff detects| TRADE[Position Change trade] TRADE -->|executed by| BT ZP -->|built by| QUANT[Quantopian] ```