Learning Objectives

What You'll Learn Today

Section 1

Why Python for Financial Modeling?

Understanding when and why to move beyond Excel

šŸ’­
Think About It

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 ForQuick ad-hoc analysis, presentations, small datasets, what-if scenariosLarge datasets (100K+ rows), repetitive tasks, automated reporting, backtesting
Data Volume1,048,576 rows max; slows down beyond 100KVirtually unlimited — limited only by RAM
ReproducibilityHard to audit formulas across 20 tabs; manual steps are invisibleEvery step is code — fully auditable, version-controlled
AutomationMacros/VBA (clunky, security warnings)Schedule scripts to run automatically (cron, Task Scheduler)
Data SourcesManual copy-paste or Power Query (limited APIs)Connect to ANY API, database, or web source with a few lines of code
Statistical AnalysisBasic (regression, correlation)Full scientific stack (scipy, statsmodels, scikit-learn)
CollaborationEmail files back and forth; merge conflictsGit version control; Jupyter notebooks for sharing
Learning CurveLow — everyone knows ExcelMedium — requires programming basics
Industry UseUniversal — every bank, PE fund, corporateQuant funds, algorithmic trading, risk management, data science teams
šŸ’”The Real-World Workflow

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:

LibraryWhat It DoesExcel EquivalentInstall Command
PandasData manipulation — DataFrames, filtering, grouping, mergingEntire Excel (rows, columns, formulas)pip install pandas
NumPyNumerical computing — arrays, matrix math, statisticsArray formulas, SUMPRODUCTpip install numpy
yfinanceFree Yahoo Finance API — stock prices, financialsManual download from Yahoo Finance websitepip install yfinance
MatplotlibCharts and visualizationsExcel chartspip install matplotlib
OpenPyXLRead/write Excel files programmaticallyN/A — it creates Excel filespip install openpyxl
JupyterInteractive notebook environment for codingN/A — it's like a smart Excel grid for codepip install jupyter
šŸš€ One-Command Install (run in terminal / command prompt):

pip install pandas numpy yfinance matplotlib openpyxl jupyter
Section 2

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:

Terminal
python --version
# Should output: Python 3.11.x or higher

In your terminal, run:

Terminal
pip install pandas numpy yfinance matplotlib openpyxl jupyter

This downloads and installs all 6 libraries. It may take 1-2 minutes. Verify:

Terminal
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:

Terminal
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:

Python — Cell 1: Import Libraries
# 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__}")
āœ… Libraries loaded successfully! Pandas version: 2.2.1
Python — Cell 2: Fetch Reliance Industries Stock Data
# 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()
Open High Low Close Volume Dividends Stock Splits Date 2024-04-26 2949.900146 2972.000000 2930.000000 2944.199951 12644627 0.0 0.0 2024-04-29 2955.000000 2974.000000 2940.000000 2961.000000 8912345 0.0 0.0 2024-04-30 2957.000000 2989.949951 2950.000000 2970.000000 9834521 0.0 0.0 2024-05-02 2960.000000 2975.000000 2942.000000 2950.000000 7823456 0.0 0.0 2024-05-03 2955.000000 2980.000000 2948.000000 2975.000000 8534521 0.0 0.0
šŸŽÆWhat Just Happened?

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 FormatExampleExchange
NSE stocks: SYMBOL.NSRELIANCE.NS, TCS.NS, INFY.NSNational Stock Exchange (India)
BSE stocks: SYMBOL.BORELIANCE.BO, TCS.BOBombay Stock Exchange (India)
US stocks: SYMBOLAAPL, MSFT, GOOGLNASDAQ / NYSE
Nifty 50 Index: ^NSEI^NSEINSE Index
Sensex: ^BSESN^BSESNBSE Index
Section 3

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

Python — Common DataFrame Operations
# 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')
šŸ’”Pandas Cheat Sheet for Finance

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.

Python — Complete TCS Analysis
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)}")
Data from 2023-04-26 to 2025-04-26 Total trading days: 491
Python — Step 2: Calculate Daily Returns
# ========================================
# 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}%")
Average daily return: 0.0387% Daily std deviation: 1.2481%
Python — Step 3: Total Return & CAGR
# ========================================
# 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")
Start Price: ₹3,294.92 End Price: ₹3,562.15 Total Return: 8.11% CAGR: 4.02% Period: 2.00 years
Python — Step 4: Volatility & Drawdown
# ========================================
# 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()}")
Annualized Volatility: 19.81% Maximum Drawdown: -12.45% Max Drawdown Date: 2024-06-04
Python — Step 5: Moving Averages
# ========================================
# 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)
Current Price: ₹3,562.15 50-day MA: ₹3,545.23 200-day MA: ₹3,489.67 Signal: 🟢 Golden Cross (Bullish) — MA50 above MA200 ================================================== TCS STOCK ANALYSIS SUMMARY ================================================== Period: 2023-04-26 to 2025-04-26 Total Return: 8.11% CAGR: 4.02% Ann. Volatility: 19.81% Max Drawdown: -12.45% Sharpe Ratio: 0.20 ==================================================
Section 4

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:

ProblemExamplePandas Solution
Missing valuesNaN, blank cells, "N/A".dropna() or .fillna()
Duplicate rowsSame 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()
OutliersStock 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.

Python — Cleaning a Messy Portfolio Dataset
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")
Raw Data: Stock Purchase_Price Shares Purchase_Date 0 RELIANCE 2,450.50 100.0 2024-01-15 1 TCS 3,294.00 50.0 15/01/2024 2 INFY 1,456.75 75.0 2024-02-01 3 HDFCBANK 1,580.00 60.0 2024-01-20 4 reliance 2,475.00 50.0 2024-02-10 5 TCS NaN 50.0 15/01/2024 6 None 987.50 NaN 2024-03-01 7 ICICIBANK 1,024.30 80.0 2024-01-25 Problems: inconsistent stock names, commas in prices, mixed date formats, missing values, duplicates
Python — Step 2-5: Clean Everything
# ========================================
# 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}")
Cleaned Data: Stock Purchase_Price Shares Purchase_Date Investment_Value 0 RELIANCE 2450.50 100.0 2024-01-15 245050.0 1 TCS 3294.00 50.0 2024-01-15 164700.0 2 INFY 1456.75 75.0 2024-02-01 109256.25 3 HDFCBANK 1580.00 60.0 2024-01-20 94800.0 4 RELIANCE 2475.00 50.0 2024-02-10 123750.0 5 ICICIBANK 1024.30 80.0 2024-01-25 81944.0 ============================================================ Total Portfolio Value: ₹819,500.25 Number of Holdings: 6 ============================================================

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.

Section 5

Basic Financial Calculations in Python

Translating your Excel financial formulas to Python

šŸ“ Key Financial Formulas — Excel vs Python

CalculationExcel FormulaPython Code
Daily Return=(B2-B1)/B1df['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 DrawdownComplex 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.

Python — Multi-Stock Comparison
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())
Python Lab

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:

  1. Download 3 years of Nifty 50 data using yfinance (ticker: ^NSEI)
  2. Calculate daily returns and plot a histogram of returns
  3. Calculate: mean daily return, standard deviation, skewness, and kurtosis
  4. Identify the best and worst trading days in the 3-year period
  5. Calculate 50-day and 200-day moving averages and identify crossover dates
Python — Nifty 50 Analysis
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:

StockTickerSharesAvg Buy Price (₹)
Reliance IndustriesRELIANCE.NS1502,400
TCSTCS.NS803,200
HDFC BankHDFCBANK.NS2001,500
InfosysINFY.NS1201,400
Bharti AirtelBHARTIARTL.NS1001,100

Tasks:

  1. Fetch current prices for all 5 stocks using yfinance
  2. Calculate current portfolio value and P&L (profit/loss) for each stock
  3. Calculate portfolio weight (%) for each stock
  4. Calculate 1-year return for each stock
  5. Export the portfolio summary to an Excel file
Python — Portfolio Tracker
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:

  1. Define a function dcf_valuation() that takes: FCFs, discount rate, terminal growth rate
  2. Calculate present value of each year's FCF
  3. Calculate terminal value using Gordon Growth Model
  4. Return the total enterprise value
  5. Test with: FCFs = [100, 120, 140, 160, 180] Cr, WACC = 12%, terminal growth = 3%
Python — DCF Model Function
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:

  1. Fetch market cap, P/E, P/B, and EV/EBITDA for TCS, Infosys, Wipro, HCL Tech, and Tech Mahindra
  2. Calculate the median and mean for each multiple
  3. Identify which stock appears cheapest on each metric
  4. Export the comparison table to Excel
Python — Automated Comps Analysis
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")
Quick Review

šŸ“š Key Terms — Click to Flip

Knowledge Check

Test Your Understanding

10 objective questions on Python for Financial Modeling

Summary

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
šŸ“šNext Session

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