What You'll Learn Today
Why Python for Financial Modeling?
Understanding when and why to move beyond Excel
You've built 18 sessions worth of models in Excel. Why would a financial analyst ever need Python? What can Python do that Excel cannot?
Think about data volume, repetition, real-time data, and auditability before we discuss
š Excel vs Python ā When to Use What
Excel remains the lingua franca of finance. You will NOT abandon Excel ā you will augment it with Python. Here's when each tool shines:
| Dimension | š Excel | š Python |
|---|---|---|
| Best For | Quick ad-hoc analysis, presentations, small datasets, what-if scenarios | Large datasets (100K+ rows), repetitive tasks, automated reporting, backtesting |
| Data Volume | 1,048,576 rows max; slows down beyond 100K | Virtually unlimited ā limited only by RAM |
| Reproducibility | Hard to audit formulas across 20 tabs; manual steps are invisible | Every step is code ā fully auditable, version-controlled |
| Automation | Macros/VBA (clunky, security warnings) | Schedule scripts to run automatically (cron, Task Scheduler) |
| Data Sources | Manual copy-paste or Power Query (limited APIs) | Connect to ANY API, database, or web source with a few lines of code |
| Statistical Analysis | Basic (regression, correlation) | Full scientific stack (scipy, statsmodels, scikit-learn) |
| Collaboration | Email files back and forth; merge conflicts | Git version control; Jupyter notebooks for sharing |
| Learning Curve | Low ā everyone knows Excel | Medium ā requires programming basics |
| Industry Use | Universal ā every bank, PE fund, corporate | Quant funds, algorithmic trading, risk management, data science teams |
In investment banking and private equity, the typical workflow is: Python for data processing ā Excel for presentation. You use Python to fetch, clean, and analyze large datasets, then export the results to Excel for formatting and presentation to clients / investment committees.
š ļø The Python Finance Ecosystem
You don't need to learn "all of Python." For financial modeling, you need these 6 core libraries:
| Library | What It Does | Excel Equivalent | Install Command |
|---|---|---|---|
| Pandas | Data manipulation ā DataFrames, filtering, grouping, merging | Entire Excel (rows, columns, formulas) | pip install pandas |
| NumPy | Numerical computing ā arrays, matrix math, statistics | Array formulas, SUMPRODUCT | pip install numpy |
| yfinance | Free Yahoo Finance API ā stock prices, financials | Manual download from Yahoo Finance website | pip install yfinance |
| Matplotlib | Charts and visualizations | Excel charts | pip install matplotlib |
| OpenPyXL | Read/write Excel files programmatically | N/A ā it creates Excel files | pip install openpyxl |
| Jupyter | Interactive notebook environment for coding | N/A ā it's like a smart Excel grid for code | pip install jupyter |
pip install pandas numpy yfinance matplotlib openpyxl jupyter
Setting Up Your Python Environment
Getting Jupyter Notebook running and writing your first financial script
š Step-by-Step Setup
Download Python from python.org (version 3.11 or later recommended).
ā ļø Windows Users: During installation, CHECK the box that says "Add Python to PATH". This is the #1 cause of setup issues.
Verify installation by opening a terminal (Command Prompt / PowerShell on Windows, Terminal on Mac) and typing:
python --version
# Should output: Python 3.11.x or higher
In your terminal, run:
pip install pandas numpy yfinance matplotlib openpyxl jupyter
This downloads and installs all 6 libraries. It may take 1-2 minutes. Verify:
python -c "import pandas; print(pandas.__version__)"
# Should output: 2.x.x
Create a folder for your financial models, navigate to it in terminal, and launch Jupyter:
mkdir finance_models
cd finance_models
jupyter notebook
Your browser will open with the Jupyter interface. Click New ā Python 3 to create a new notebook.
š” Jupyter Basics: A notebook has cells. Type code in a cell, press Shift+Enter to run it. The output appears below. This is where you'll do all your financial modeling.
āļø Your First Financial Script ā Fetch Live Stock Data
Type this in your Jupyter notebook and press Shift+Enter:
# Cell 1: Import the libraries we need
import pandas as pd
import yfinance as yf
print("ā
Libraries loaded successfully!")
print(f"Pandas version: {pd.__version__}")
# Cell 2: Download 1 year of Reliance stock data from Yahoo Finance
ticker = "RELIANCE.NS" # .NS = NSE (National Stock Exchange)
reliance = yf.Ticker(ticker)
# Get historical prices
hist = reliance.history(period="1y")
# Show the first 5 rows
hist.head()
With 3 lines of code, you fetched an entire year of Reliance Industries stock data ā Open, High, Low, Close, Volume ā directly from Yahoo Finance. No manual download, no copy-paste, no CSV file. This data is live and updates every time you run the cell.
| Yahoo Finance Ticker Format | Example | Exchange |
|---|---|---|
NSE stocks: SYMBOL.NS | RELIANCE.NS, TCS.NS, INFY.NS | National Stock Exchange (India) |
BSE stocks: SYMBOL.BO | RELIANCE.BO, TCS.BO | Bombay Stock Exchange (India) |
US stocks: SYMBOL | AAPL, MSFT, GOOGL | NASDAQ / NYSE |
Nifty 50 Index: ^NSEI | ^NSEI | NSE Index |
Sensex: ^BSESN | ^BSESN | BSE Index |
Pandas Essentials for Financial Data
The DataFrame is your new spreadsheet ā learn to wield it
š What is a DataFrame?
A DataFrame is Pandas' version of a spreadsheet ā rows and columns of data. The key difference: you manipulate it with code, not mouse clicks.
š In Excel
- Select column B ā Click "Sort AāZ"
- =AVERAGE(B2:B252)
- Filter ā Close > 2900
- Insert ā Pivot Table
š In Pandas
df.sort_values('Close')df['Close'].mean()df[df['Close'] > 2900]df.pivot_table(...)
š 10 Essential Pandas Operations for Finance
# Assuming 'hist' is our Reliance stock DataFrame from earlier
# 1. Shape ā how many rows and columns?
print(f"Rows: {hist.shape[0]}, Columns: {hist.shape[1]}")
# 2. Column names and data types
print(hist.dtypes)
# 3. Statistical summary (like Excel's descriptive statistics)
print(hist.describe())
# 4. Select a single column (like clicking a column in Excel)
close_prices = hist['Close']
# 5. Calculate daily returns (% change from previous day)
daily_returns = hist['Close'].pct_change()
# 6. Filter rows ā only days where Close > 3000
high_days = hist[hist['Close'] > 3000]
# 7. Sort by Close price (descending)
sorted_data = hist.sort_values('Close', ascending=False)
# 8. Date-based slicing ā get data for a specific month
jan_data = hist.loc['2025-01-01':'2025-01-31']
# 9. Moving average (like Excel's AVERAGE with shifting range)
hist['MA_50'] = hist['Close'].rolling(window=50).mean()
hist['MA_200'] = hist['Close'].rolling(window=200).mean()
# 10. Export to Excel
hist.to_excel('reliance_analysis.xlsx')
Remember: Every Excel formula has a Pandas equivalent:
=SUM() ā .sum() |
=AVERAGE() ā .mean() |
=STDEV() ā .std() |
=MAX() ā .max() |
=MIN() ā .min() |
=COUNT() ā .count() |
=PERCENTILE() ā .quantile() |
VLOOKUP ā .merge()
āļø Worked Example 1: Complete Stock Analysis of TCS
Given: Fetch TCS (Tata Consultancy Services) stock data for the last 2 years and perform a comprehensive analysis.
Calculate: (i) Total return over the period. (ii) Annualized return (CAGR). (iii) Annualized volatility. (iv) Maximum drawdown. (v) 50-day and 200-day moving averages.
import pandas as pd
import numpy as np
import yfinance as yf
# ========================================
# Step 1: Fetch TCS Data (2 years)
# ========================================
tcs = yf.Ticker("TCS.NS")
df = tcs.history(period="2y")
print(f"Data from {df.index[0].date()} to {df.index[-1].date()}")
print(f"Total trading days: {len(df)}")
# ========================================
# Step 2: Daily Returns
# ========================================
df['Daily_Return'] = df['Close'].pct_change()
# Drop the first row (NaN from pct_change)
df = df.dropna()
print(f"Average daily return: {df['Daily_Return'].mean()*100:.4f}%")
print(f"Daily std deviation: {df['Daily_Return'].std()*100:.4f}%")
# ========================================
# Step 3: Total Return and CAGR
# ========================================
start_price = df['Close'].iloc[0]
end_price = df['Close'].iloc[-1]
# (i) Total Return
total_return = (end_price - start_price) / start_price * 100
# (ii) CAGR (annualized return)
years = (df.index[-1] - df.index[0]).days / 365.25
cagr = ((end_price / start_price) ** (1/years) - 1) * 100
print(f"Start Price: ā¹{start_price:,.2f}")
print(f"End Price: ā¹{end_price:,.2f}")
print(f"Total Return: {total_return:.2f}%")
print(f"CAGR: {cagr:.2f}%")
print(f"Period: {years:.2f} years")
# ========================================
# Step 4: Annualized Volatility & Max Drawdown
# ========================================
trading_days = 252 # Standard for Indian markets
# (iii) Annualized Volatility
annual_vol = df['Daily_Return'].std() * np.sqrt(trading_days) * 100
# (iv) Maximum Drawdown
df['Cumulative_Max'] = df['Close'].cummax()
df['Drawdown'] = (df['Close'] - df['Cumulative_Max']) / df['Cumulative_Max'] * 100
max_drawdown = df['Drawdown'].min()
# Date of maximum drawdown
max_dd_date = df['Drawdown'].idxmin()
print(f"Annualized Volatility: {annual_vol:.2f}%")
print(f"Maximum Drawdown: {max_drawdown:.2f}%")
print(f"Max Drawdown Date: {max_dd_date.date()}")
# ========================================
# Step 5: Moving Averages
# ========================================
# (v) 50-day and 200-day moving averages
df['MA_50'] = df['Close'].rolling(window=50).mean()
df['MA_200'] = df['Close'].rolling(window=200).mean()
# Current values
current_ma50 = df['MA_50'].iloc[-1]
current_ma200 = df['MA_200'].iloc[-1]
current_price = df['Close'].iloc[-1]
# Golden Cross / Death Cross signal
if current_ma50 > current_ma200:
signal = "š¢ Golden Cross (Bullish) ā MA50 above MA200"
else:
signal = "š“ Death Cross (Bearish) ā MA50 below MA200"
print(f"Current Price: ā¹{current_price:,.2f}")
print(f"50-day MA: ā¹{current_ma50:,.2f}")
print(f"200-day MA: ā¹{current_ma200:,.2f}")
print(f"Signal: {signal}")
# ========================================
# SUMMARY TABLE
# ========================================
print("\n" + "="*50)
print("TCS STOCK ANALYSIS SUMMARY")
print("="*50)
print(f"Period: {df.index[0].date()} to {df.index[-1].date()}")
print(f"Total Return: {total_return:.2f}%")
print(f"CAGR: {cagr:.2f}%")
print(f"Ann. Volatility: {annual_vol:.2f}%")
print(f"Max Drawdown: {max_drawdown:.2f}%")
print(f"Sharpe Ratio: {cagr/annual_vol:.2f}")
print("="*50)
Data Cleaning with Pandas
Real-world financial data is messy ā learn to clean it like a pro
š Why Data Cleaning Matters
In the real world, financial data is never clean. You'll encounter:
| Problem | Example | Pandas Solution |
|---|---|---|
| Missing values | NaN, blank cells, "N/A" | .dropna() or .fillna() |
| Duplicate rows | Same stock price entered twice | .drop_duplicates() |
| Wrong data types | "1,234.56" as text instead of number | .astype(float) |
| Date formatting | "26/04/2025" vs "2025-04-26" | pd.to_datetime() |
| Outliers | Stock price of ā¹99,999 (data error) | .clip() or IQR filtering |
| Inconsistent names | "Reliance" vs "RELIANCE" vs "Reliance Ind." | .replace() or .str.upper() |
āļø Worked Example 2: Cleaning a Messy Stock Portfolio
Given: A portfolio CSV file with common data quality issues.
Tasks: (i) Load the CSV data. (ii) Fix data types and remove commas. (iii) Handle missing values. (iv) Remove duplicates. (v) Calculate portfolio value.
import pandas as pd
import numpy as np
# ========================================
# Step 1: Create messy data (simulating a real-world CSV)
# ========================================
messy_data = {
'Stock': ['RELIANCE', 'TCS', 'INFY', 'HDFCBANK', 'reliance', 'TCS', None, 'ICICIBANK'],
'Purchase_Price': ['2,450.50', '3,294.00', '1,456.75', '1,580.00', '2475.00', np.nan, '987.50', '1,024.30'],
'Shares': [100, 50, 75, 60, 50, 50, np.nan, 80],
'Purchase_Date': ['2024-01-15', '15/01/2024', '2024-02-01', '2024-01-20', '2024-02-10', '15/01/2024', '2024-03-01', '2024-01-25']
}
df = pd.DataFrame(messy_data)
print("Raw Data:")
print(df)
print(f"\nProblems: inconsistent stock names, commas in prices, mixed date formats, missing values, duplicates")
# ========================================
# Step 2: Fix Stock Names (uppercase + strip whitespace)
# ========================================
df['Stock'] = df['Stock'].str.upper().str.strip()
# ========================================
# Step 3: Remove rows with missing Stock or Shares
# ========================================
df = df.dropna(subset=['Stock', 'Shares'])
# ========================================
# Step 4: Fix Purchase_Price (remove commas, convert to float)
# ========================================
df['Purchase_Price'] = df['Purchase_Price'].astype(str).str.replace(',', '')
df['Purchase_Price'] = pd.to_numeric(df['Purchase_Price'], errors='coerce')
# Drop rows where price couldn't be converted
df = df.dropna(subset=['Purchase_Price'])
# ========================================
# Step 5: Fix Dates (mixed formats ā standard datetime)
# ========================================
df['Purchase_Date'] = pd.to_datetime(df['Purchase_Date'], format='mixed', dayfirst=True)
# ========================================
# Step 6: Remove duplicates (same stock + same date + same shares)
# ========================================
df = df.drop_duplicates(subset=['Stock', 'Purchase_Date'], keep='first')
# ========================================
# Step 7: Calculate Portfolio Value
# ========================================
df['Investment_Value'] = df['Purchase_Price'] * df['Shares']
# Reset index
df = df.reset_index(drop=True)
print("Cleaned Data:")
print(df.to_string())
print(f"\n{'='*60}")
print(f"Total Portfolio Value: ā¹{df['Investment_Value'].sum():,.2f}")
print(f"Number of Holdings: {len(df)}")
print(f"{'='*60}")
Key Takeaway: We went from 8 messy rows ā 6 clean rows. We removed 1 row with missing stock name, 1 row with missing price, fixed date formats, standardized stock names, and removed true duplicates ā all in about 15 lines of code.
Basic Financial Calculations in Python
Translating your Excel financial formulas to Python
š Key Financial Formulas ā Excel vs Python
| Calculation | Excel Formula | Python Code |
|---|---|---|
| Daily Return | =(B2-B1)/B1 | df['Close'].pct_change() |
| Cumulative Return | =(1+C2)*(1+C3)...-1 | (1+df['Return']).cumprod()-1 |
| Mean Return | =AVERAGE(C:C) | df['Return'].mean() |
| Volatility (Annual) | =STDEV(C:C)*SQRT(252) | df['Return'].std() * np.sqrt(252) |
| Sharpe Ratio | =(AvgReturn-Rf)/StdDev | (df['Return'].mean() - rf) / df['Return'].std() |
| Max Drawdown | Complex array formula | (df['Close']/df['Close'].cummax()-1).min() |
| CAGR | =(End/Start)^(1/Years)-1 | (end/start)**(1/years)-1 |
| Correlation | =CORREL(B:B, C:C) | df['StockA'].corr(df['StockB']) |
| Beta | =SLOPE(Stock,Market) | np.cov(stock, market)[0,1]/np.var(market) |
āļø Worked Example 3: Compare Indian IT Stocks
Given: Fetch 1-year data for TCS, Infosys, and Wipro.
Calculate: (i) Total return for each stock. (ii) Annualized volatility. (iii) Correlation matrix. (iv) Beta relative to Nifty 50.
import pandas as pd
import numpy as np
import yfinance as yf
# ========================================
# Step 1: Download data for 3 IT stocks + Nifty 50
# ========================================
tickers = ['TCS.NS', 'INFY.NS', 'WIPRO.NS', '^NSEI']
data = yf.download(tickers, period='1y')['Close']
# Drop any rows with missing data
data = data.dropna()
print("Stock Data (first 5 rows):")
print(data.head())
# ========================================
# Step 2: Calculate daily returns
# ========================================
returns = data.pct_change().dropna()
# ========================================
# Step 3: Total Return
# ========================================
total_returns = (data.iloc[-1] / data.iloc[0] - 1) * 100
print("\n" + "="*60)
print("TOTAL RETURNS (1 Year)")
print("="*60)
for ticker in tickers:
name = ticker.replace('.NS', '').replace('^', '')
print(f"{name:12s}: {total_returns[ticker]:+8.2f}%")
# ========================================
# Step 4: Annualized Volatility
# ========================================
ann_vol = returns.std() * np.sqrt(252) * 100
print("\n" + "="*60)
print("ANNUALIZED VOLATILITY")
print("="*60)
for ticker in tickers:
name = ticker.replace('.NS', '').replace('^', '')
print(f"{name:12s}: {ann_vol[ticker]:8.2f}%")
# ========================================
# Step 5: Correlation Matrix
# ========================================
print("\n" + "="*60)
print("CORRELATION MATRIX")
print("="*60)
corr = returns.corr()
print(corr.round(3).to_string())
# ========================================
# Step 6: Beta (relative to Nifty 50)
# ========================================
nifty_var = returns['^NSEI'].var()
print("\n" + "="*60)
print("BETA (relative to Nifty 50)")
print("="*60)
for ticker in ['TCS.NS', 'INFY.NS', 'WIPRO.NS']:
name = ticker.replace('.NS', '')
covariance = returns[ticker].cov(returns['^NSEI'])
beta = covariance / nifty_var
print(f"{name:12s}: Beta = {beta:.3f}")
# ========================================
# SUMMARY TABLE
# ========================================
summary = pd.DataFrame({
'Total Return (%)': total_returns.values,
'Ann. Volatility (%)': ann_vol.values,
}, index=['TCS', 'INFY', 'WIPRO', 'NIFTY50'])
print("\n" + "="*60)
print("FINAL SUMMARY")
print("="*60)
print(summary.round(2).to_string())
Hands-On Practice Exercises
Build your Python financial modeling skills step by step
šļø Exercise 1: Nifty 50 Index Analysis (20 min)
Objective: Fetch Nifty 50 data and perform basic statistical analysis
Tasks:
- Download 3 years of Nifty 50 data using yfinance (ticker:
^NSEI) - Calculate daily returns and plot a histogram of returns
- Calculate: mean daily return, standard deviation, skewness, and kurtosis
- Identify the best and worst trading days in the 3-year period
- Calculate 50-day and 200-day moving averages and identify crossover dates
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
# 1. Download 3 years of Nifty 50 data
nifty = yf.Ticker("^NSEI")
df = nifty.history(period="3y")
# 2. Daily returns
df['Return'] = df['Close'].pct_change()
df = df.dropna()
# Plot histogram
plt.figure(figsize=(10, 5))
plt.hist(df['Return'] * 100, bins=50, edgecolor='black', color='#2563EB', alpha=0.7)
plt.title('Nifty 50 Daily Returns Distribution (3 Years)')
plt.xlabel('Daily Return (%)')
plt.ylabel('Frequency')
plt.axvline(x=df['Return'].mean()*100, color='red', linestyle='--', label=f'Mean: {df["Return"].mean()*100:.3f}%')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 3. Statistics
print("="*50)
print("NIFTY 50 RETURN STATISTICS")
print("="*50)
print(f"Mean Daily Return: {df['Return'].mean()*100:.4f}%")
print(f"Std Dev (Daily): {df['Return'].std()*100:.4f}%")
print(f"Skewness: {df['Return'].skew():.4f}")
print(f"Kurtosis: {df['Return'].kurtosis():.4f}")
print(f"Annualized Return: {df['Return'].mean()*252*100:.2f}%")
print(f"Annualized Vol: {df['Return'].std()*np.sqrt(252)*100:.2f}%")
# 4. Best and worst days
best_day = df.loc[df['Return'].idxmax()]
worst_day = df.loc[df['Return'].idxmin()]
print(f"\nBest Day: {best_day.name.date()} ā {best_day['Return']*100:+.2f}%")
print(f"Worst Day: {worst_day.name.date()} ā {worst_day['Return']*100:+.2f}%")
# 5. Moving averages and crossovers
df['MA50'] = df['Close'].rolling(50).mean()
df['MA200'] = df['Close'].rolling(200).mean()
df = df.dropna()
# Golden Cross: MA50 crosses above MA200
df['Signal'] = np.where(df['MA50'] > df['MA200'], 1, -1)
df['Crossover'] = df['Signal'].diff()
golden_crosses = df[df['Crossover'] == 2] # MA50 crosses above MA200
death_crosses = df[df['Crossover'] == -2] # MA50 crosses below MA200
print(f"\nGolden Crosses: {len(golden_crosses)}")
for date in golden_crosses.index:
print(f" ā {date.date()}")
print(f"\nDeath Crosses: {len(death_crosses)}")
for date in death_crosses.index:
print(f" ā {date.date()}")
šļø Exercise 2: Portfolio Analytics (25 min)
Objective: Build a simple portfolio tracker that fetches live prices and calculates key metrics
Scenario: You manage a ā¹50 lakh portfolio with the following holdings:
| Stock | Ticker | Shares | Avg Buy Price (ā¹) |
|---|---|---|---|
| Reliance Industries | RELIANCE.NS | 150 | 2,400 |
| TCS | TCS.NS | 80 | 3,200 |
| HDFC Bank | HDFCBANK.NS | 200 | 1,500 |
| Infosys | INFY.NS | 120 | 1,400 |
| Bharti Airtel | BHARTIARTL.NS | 100 | 1,100 |
Tasks:
- Fetch current prices for all 5 stocks using yfinance
- Calculate current portfolio value and P&L (profit/loss) for each stock
- Calculate portfolio weight (%) for each stock
- Calculate 1-year return for each stock
- Export the portfolio summary to an Excel file
import pandas as pd
import yfinance as yf
# Define portfolio
portfolio = {
'Stock': ['Reliance', 'TCS', 'HDFC Bank', 'Infosys', 'Bharti Airtel'],
'Ticker': ['RELIANCE.NS', 'TCS.NS', 'HDFCBANK.NS', 'INFY.NS', 'BHARTIARTL.NS'],
'Shares': [150, 80, 200, 120, 100],
'Buy_Price': [2400, 3200, 1500, 1400, 1100]
}
df = pd.DataFrame(portfolio)
# 1. Fetch current prices
current_prices = {}
for ticker in df['Ticker']:
stock = yf.Ticker(ticker)
price = stock.history(period='1d')['Close'].iloc[-1]
current_prices[ticker] = price
df['Current_Price'] = df['Ticker'].map(current_prices)
# 2. P&L calculations
df['Invested'] = df['Shares'] * df['Buy_Price']
df['Current_Value'] = df['Shares'] * df['Current_Price']
df['PnL'] = df['Current_Value'] - df['Invested']
df['PnL_Pct'] = (df['Current_Price'] / df['Buy_Price'] - 1) * 100
# 3. Portfolio weights
total_value = df['Current_Value'].sum()
df['Weight_Pct'] = df['Current_Value'] / total_value * 100
# 4. Fetch 1-year returns
for idx, row in df.iterrows():
hist = yf.Ticker(row['Ticker']).history(period='1y')
if len(hist) > 0:
yr_return = (hist['Close'].iloc[-1] / hist['Close'].iloc[0] - 1) * 100
df.at[idx, 'Y1_Return_Pct'] = yr_return
# Display
print("="*80)
print("PORTFOLIO SUMMARY")
print("="*80)
print(df[['Stock', 'Shares', 'Buy_Price', 'Current_Price', 'Invested', 'Current_Value', 'PnL', 'PnL_Pct']].to_string(index=False))
print(f"\nTotal Invested: ā¹{df['Invested'].sum():>12,.2f}")
print(f"Current Value: ā¹{df['Current_Value'].sum():>12,.2f}")
print(f"Total P&L: ā¹{df['PnL'].sum():>12,.2f}")
print(f"Overall Return: {(df['Current_Value'].sum()/df['Invested'].sum()-1)*100:>11.2f}%")
# 5. Export to Excel
df.to_excel('portfolio_summary.xlsx', index=False)
print("\nā
Exported to portfolio_summary.xlsx")
šļø Exercise 3: Building a DCF Model Skeleton in Python (25 min)
Objective: Create a programmatic DCF framework that you can reuse for any company
Tasks:
- Define a function
dcf_valuation()that takes: FCFs, discount rate, terminal growth rate - Calculate present value of each year's FCF
- Calculate terminal value using Gordon Growth Model
- Return the total enterprise value
- Test with: FCFs = [100, 120, 140, 160, 180] Cr, WACC = 12%, terminal growth = 3%
import numpy as np
import pandas as pd
def dcf_valuation(fcfs, wacc, terminal_growth, shares_outstanding=None, net_debt=0):
"""
Perform DCF valuation.
Parameters:
- fcfs: list of projected free cash flows (ā¹ Cr)
- wacc: weighted average cost of capital (decimal, e.g., 0.12 for 12%)
- terminal_growth: perpetual growth rate (decimal, e.g., 0.03 for 3%)
- shares_outstanding: in Cr (optional, for per-share value)
- net_debt: net debt in ā¹ Cr (for equity value)
Returns: DataFrame with detailed calculations
"""
n = len(fcfs)
years = list(range(1, n + 1))
# Present Value of each FCF
pv_fcfs = [fcf / (1 + wacc) ** t for fcf, t in zip(fcfs, years)]
# Terminal Value (Gordon Growth Model)
terminal_fcf = fcfs[-1] * (1 + terminal_growth)
terminal_value = terminal_fcf / (wacc - terminal_growth)
pv_terminal = terminal_value / (1 + wacc) ** n
# Enterprise Value
ev = sum(pv_fcfs) + pv_terminal
# Equity Value
equity_value = ev - net_debt
# Build results DataFrame
results = pd.DataFrame({
'Year': years,
'Projected_FCF': fcfs,
'Discount_Factor': [1/(1+wacc)**t for t in years],
'PV_of_FCF': pv_fcfs
})
# Summary
print("="*60)
print("DCF VALUATION SUMMARY")
print("="*60)
print(f"WACC: {wacc*100:.1f}%")
print(f"Terminal Growth Rate: {terminal_growth*100:.1f}%")
print(f"Projection Period: {n} years")
print(f"")
print(f"Sum of PV of FCFs: ā¹{sum(pv_fcfs):>10,.2f} Cr")
print(f"Terminal Value: ā¹{terminal_value:>10,.2f} Cr")
print(f"PV of Terminal Value: ā¹{pv_terminal:>10,.2f} Cr")
print(f"")
print(f"Enterprise Value: ā¹{ev:>10,.2f} Cr")
print(f"Net Debt: ā¹{net_debt:>10,.2f} Cr")
print(f"Equity Value: ā¹{equity_value:>10,.2f} Cr")
if shares_outstanding:
price_per_share = equity_value / shares_outstanding
print(f"Shares Outstanding: {shares_outstanding:>10,.2f} Cr")
print(f"Implied Share Price: ā¹{price_per_share:>10,.2f}")
print("="*60)
return results
# ========================================
# Test with given data
# ========================================
fcfs = [100, 120, 140, 160, 180] # ā¹ Cr
wacc = 0.12 # 12%
terminal_growth = 0.03 # 3%
results = dcf_valuation(
fcfs=fcfs,
wacc=wacc,
terminal_growth=terminal_growth,
shares_outstanding=10, # 10 Cr shares
net_debt=50 # ā¹50 Cr net debt
)
print("\nDetailed Year-by-Year:")
print(results.round(2).to_string(index=False))
šļø Exercise 4 (Advanced): Automated Comparable Company Analysis (20 min)
Objective: Fetch financial data for a peer group and calculate valuation multiples programmatically
Scenario: Compare 5 Indian IT companies on key valuation multiples.
Tasks:
- Fetch market cap, P/E, P/B, and EV/EBITDA for TCS, Infosys, Wipro, HCL Tech, and Tech Mahindra
- Calculate the median and mean for each multiple
- Identify which stock appears cheapest on each metric
- Export the comparison table to Excel
import pandas as pd
import yfinance as yf
# Define peer group
companies = {
'TCS': 'TCS.NS',
'Infosys': 'INFY.NS',
'Wipro': 'WIPRO.NS',
'HCL Tech': 'HCLTECH.NS',
'Tech Mahindra': 'TECHM.NS'
}
# Fetch key metrics for each company
data = []
for name, ticker in companies.items():
stock = yf.Ticker(ticker)
info = stock.info
data.append({
'Company': name,
'Ticker': ticker,
'Market Cap (ā¹ Cr)': info.get('marketCap', 0) / 1e7, # Convert to Cr
'P/E Ratio': info.get('trailingPE', None),
'P/B Ratio': info.get('priceToBook', None),
'EV/EBITDA': info.get('enterpriseToEbitda', None),
'Dividend Yield %': info.get('dividendYield', 0) * 100 if info.get('dividendYield') else None,
'ROE %': info.get('returnOnEquity', 0) * 100 if info.get('returnOnEquity') else None,
'Revenue Growth %': info.get('revenueGrowth', 0) * 100 if info.get('revenueGrowth') else None,
})
df = pd.DataFrame(data)
# Display comparison
print("="*80)
print("INDIAN IT SECTOR ā COMPARABLE COMPANY ANALYSIS")
print("="*80)
print(df.to_string(index=False))
# Summary statistics
print("\n" + "="*80)
print("SUMMARY STATISTICS")
print("="*80)
metrics = ['P/E Ratio', 'P/B Ratio', 'EV/EBITDA']
for metric in metrics:
print(f"\n{metric}:")
print(f" Median: {df[metric].median():.2f}")
print(f" Mean: {df[metric].mean():.2f}")
cheapest = df.loc[df[metric].idxmin(), 'Company']
print(f" Cheapest: {cheapest} ({df[metric].min():.2f})")
# Export to Excel
df.to_excel('it_sector_comps.xlsx', index=False)
print("\nā
Exported to it_sector_comps.xlsx")
š Key Terms ā Click to Flip
Test Your Understanding
10 objective questions on Python for Financial Modeling
Key Takeaways
š What We Covered Today
- Python augments Excel ā it doesn't replace it. Use Python for data fetching, cleaning, and automation; Excel for presentation
- The 6 core libraries for finance: Pandas, NumPy, yfinance, Matplotlib, OpenPyXL, Jupyter
- DataFrames are the Python equivalent of spreadsheets ā learn the key operations: filtering, grouping, merging, and aggregation
- yfinance provides free, programmatic access to stock prices, financials, and key metrics ā no API key needed
- Key financial calculations in Python: returns, volatility, Sharpe ratio, max drawdown, moving averages, beta, correlation
- Data cleaning is 60-80% of a financial analyst's work ā Pandas makes it 10x faster than manual Excel cleaning
- You can build reusable functions (like DCF valuation) that work for any company ā this is the real power of Python over Excel
Session 20: Python for Financial Modeling ā II
We'll build on today's foundation to create a full DCF model in Python, automate sensitivity analysis with data tables, and create professional visualizations with Matplotlib. Read: Hilpisch Ch. 7ā8