1. LEARNING OBJECTIVES
By the end of this lesson, you will be able to:
-
Set up a Python development environment for financial computing.
-
Understand and apply Python’s core data types (int, float, str, bool, list, tuple, dict, set).
-
Implement control flow using conditionals (if-elif-else) and loops (for, while).
-
Write modular code using functions with parameters, return values, and docstrings.
-
Apply exception handling (try-except-finally) to build robust financial applications.
-
Understand and implement Object-Oriented Programming (OOP) concepts: classes, objects, inheritance, polymorphism, encapsulation.
-
Design a simple financial instrument class hierarchy (e.g., Bond, Stock, Option).
-
Write and read files (CSV, JSON, Excel) for financial data processing.
2. SETTING UP THE PYTHON ENVIRONMENT FOR FINANCE
2.1 Installing Python
Download and install Python from python.org. Use version 3.8 or higher for compatibility with financial libraries.
2.2 Package Management with pip
pip is the package installer for Python. Install essential packages:
pip install numpy pandas matplotlib scipy scikit-learn statsmodels requests openpyxl
2.3 Virtual Environments
Create isolated environments to manage dependencies:
python -m venv fintech_env source fintech_env/bin/activate # On Mac/Linux fintech_env\Scripts\activate # On Windows
2.4 The Interactive Shell (REPL)
Use the Python REPL (Read-Eval-Print Loop) for quick testing:
python >>> 2 + 2 4 >>> import numpy as np >>> np.array([1, 2, 3])
2.5 Integrated Development Environments (IDEs)
-
VS Code: Popular, free, with excellent Python support.
-
PyCharm: Full-featured IDE with integrated scientific tools.
-
Jupyter Notebook: Interactive environment for data exploration and visualization.
3. PYTHON DATA TYPES – THE BUILDING BLOCKS
3.1 Numeric Types
int: Whole numbers (arbitrary precision).
>>> x = 42 >>> type(x) <class 'int'>
float: Floating-point numbers (double precision, IEEE 754).
>>> y = 3.14159 >>> type(y) <class 'float'>
complex: Complex numbers.
>>> z = 1 + 2j >>> type(z) <class 'complex'>
3.2 String (str)
Immutable sequences of characters.
>>> s = "Financial Technology" >>> s.upper() 'FINANCIAL TECHNOLOGY' >>> s.split() ['Financial', 'Technology'] >>> len(s) 20
String formatting for financial reports:
price = 123.4567 print(f"Stock price: ${price:.2f}") # Stock price: $123.46
3.3 Boolean (bool)
True and False values.
>>> is_profitable = True >>> type(is_profitable) <class 'bool'>
3.4 List (list)
Mutable, ordered sequences.
>>> prices = [100, 102, 98, 105, 110] >>> prices.append(115) >>> prices[0] 100 >>> len(prices) 6 >>> prices[-1] # Last element 115
3.5 Tuple (tuple)
Immutable, ordered sequences.
>>> stock_data = ("AAPL", 150.25, 1000000) >>> symbol, price, volume = stock_data # Tuple unpacking >>> print(symbol) AAPL
3.6 Dictionary (dict)
Key-value pairs (hash maps). Essential for financial data structures.
>>> portfolio = { ... "AAPL": {"shares": 100, "price": 150.25}, ... "GOOGL": {"shares": 50, "price": 2800.00}, ... "MSFT": {"shares": 75, "price": 330.50} ... } >>> portfolio["AAPL"]["price"] 150.25 >>> portfolio.keys() dict_keys(['AAPL', 'GOOGL', 'MSFT'])
3.7 Set (set)
Unordered collections of unique elements.
>>> unique_assets = {"AAPL", "GOOGL", "MSFT", "AAPL"} >>> unique_assets {'AAPL', 'GOOGL', 'MSFT'} >>> "AAPL" in unique_assets True
3.8 NoneType
Represents the absence of a value.
>>> price = None >>> price is None True
4. CONTROL FLOW – CONDITIONALS AND LOOPS
4.1 if-elif-else Statements
def investment_recommendation(price, moving_average): if price > moving_average * 1.1: return "STRONG BUY" elif price > moving_average: return "BUY" elif price < moving_average * 0.9: return "STRONG SELL" elif price < moving_average: return "SELL" else: return "HOLD"
4.2 for Loops
Iterating over sequences:
prices = [100, 102, 98, 105, 110] # Basic iteration for price in prices: print(price) # With index for i, price in enumerate(prices): print(f"Day {i+1}: ${price:.2f}") # Dictionary iteration for symbol, data in portfolio.items(): print(f"{symbol}: {data['shares']} shares at ${data['price']:.2f}")
4.3 while Loops
def compound_interest(principal, rate, target): years = 0 while principal < target: principal *= (1 + rate) years += 1 return years
4.4 List Comprehensions
A powerful Python feature for transforming data:
# Compute daily returns prices = [100, 102, 98, 105, 110] returns = [(prices[i+1] - prices[i]) / prices[i] for i in range(len(prices)-1)] # returns = [0.02, -0.0392, 0.0714, 0.0476] # Filter positive returns positive_returns = [r for r in returns if r > 0]
4.5 Dictionary Comprehensions
returns_dict = {f"Day_{i}": r for i, r in enumerate(returns)}
5. FUNCTIONS – MODULAR AND REUSABLE CODE
5.1 Function Definition
def calculate_sharpe_ratio(returns, risk_free_rate=0.02): """ Calculate the Sharpe ratio of a set of returns. Parameters: returns (list/array): Historical returns risk_free_rate (float): Risk-free rate (default 0.02) Returns: float: Sharpe ratio """ import numpy as np excess_returns = returns - risk_free_rate return np.mean(excess_returns) / np.std(excess_returns)
5.2 Default Parameters
def discount_cash_flows(cash_flows, discount_rate=0.10): return sum(cf / (1 + discount_rate)**i for i, cf in enumerate(cash_flows))
**5.3 *args and kwargs
For variable arguments:
def print_portfolio(*assets): for asset in assets: print(asset) def portfolio_summary(**data): for key, value in data.items(): print(f"{key}: {value}")
5.4 Lambda Functions
Anonymous functions for quick calculations:
# Sort by price prices = [100, 102, 98, 105, 110] sorted_prices = sorted(prices, key=lambda x: -x) # Sort descending # Map function daily_returns = list(map(lambda x: (x - 100) / 100, prices))
5.5 Decorators
For logging, timing, and validation:
import time def timer(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} took {end - start:.4f} seconds") return result return wrapper @timer def monte_carlo_simulation(n_paths=100000): # Simulation code return
6. EXCEPTION HANDLING – BUILDING ROBUST APPLICATIONS
6.1 Basic try-except
def safe_division(numerator, denominator): try: return numerator / denominator except ZeroDivisionError: return float('inf') except TypeError: return None
6.2 Multiple Exception Types
def parse_stock_price(price_str): try: return float(price_str) except ValueError: print(f"Invalid price: {price_str}") return 0.0 except Exception as e: print(f"Unexpected error: {e}") return None
6.3 try-except-else-finally
def process_transaction(transaction): try: # Attempt to process result = execute_transfer(transaction) except InsufficientFundsError: result = "FAILED: Insufficient funds" except NetworkError: result = "FAILED: Network issue" else: # Runs only if no exception result = "SUCCESS" finally: # Always runs (e.g., logging, closing connections) log_transaction(transaction, result) return result
7. OBJECT-ORIENTED PROGRAMMING (OOP)
7.1 Classes and Objects
class FinancialInstrument: """Base class for financial instruments.""" def __init__(self, symbol, price): self.symbol = symbol self._price = price self.history = [] self._update_history() def _update_history(self): """Internal method to track price history.""" self.history.append(self._price) @property def price(self): return self._price @price.setter def price(self, new_price): self._price = new_price self._update_history() def current_value(self): return self._price def __str__(self): return f"{self.symbol}: ${self._price:.2f}" def __repr__(self): return f"FinancialInstrument('{self.symbol}', {self._price})"
7.2 Inheritance and Polymorphism
class Stock(FinancialInstrument): """Stock class inheriting from FinancialInstrument.""" def __init__(self, symbol, price, shares_outstanding): super().__init__(symbol, price) self.shares_outstanding = shares_outstanding def market_cap(self): return self._price * self.shares_outstanding def current_value(self): return self._price def __str__(self): return f"Stock {self.symbol}: ${self._price:.2f}, Mkt Cap: ${self.market_cap():.2e}" class Bond(FinancialInstrument): """Bond class inheriting from FinancialInstrument.""" def __init__(self, symbol, price, face_value, coupon_rate, maturity): super().__init__(symbol, price) self.face_value = face_value self.coupon_rate = coupon_rate self.maturity = maturity def annual_coupon(self): return self.face_value * self.coupon_rate def yield_to_maturity(self): # Simplified YTM calculation annual_coupon = self.annual_coupon() return (annual_coupon + (self.face_value - self._price) / self.maturity) / ((self.face_value + self._price) / 2) def current_value(self): # Bonds are valued at face value + accrued interest return self._price class Option(FinancialInstrument): """Option class inheriting from FinancialInstrument.""" def __init__(self, symbol, price, strike, expiry, option_type): super().__init__(symbol, price) self.strike = strike self.expiry = expiry self.option_type = option_type # 'call' or 'put' def intrinsic_value(self, spot_price): if self.option_type == 'call': return max(spot_price - self.strike, 0) else: return max(self.strike - spot_price, 0) def current_value(self): # In practice, this would use a pricing model like Black-Scholes return self._price
7.3 Polymorphism Example
def analyze_instrument(instrument): """Polymorphic function to analyze any financial instrument.""" print(f"Instrument: {instrument}") print(f"Current Value: ${instrument.current_value():.2f}") if isinstance(instrument, Stock): print(f"Market Cap: ${instrument.market_cap():.2e}") elif isinstance(instrument, Bond): print(f"YTM: {instrument.yield_to_maturity()*100:.2f}%") elif isinstance(instrument, Option): print(f"Intrinsic Value: ${instrument.intrinsic_value(100):.2f}")
7.4 Magic Methods (Dunders)
class Portfolio: def __init__(self, name): self.name = name self.instruments = [] self.cash = 0.0 def __len__(self): return len(self.instruments) def __getitem__(self, key): if isinstance(key, int): return self.instruments[key] elif isinstance(key, str): return [inst for inst in self.instruments if inst.symbol == key] def __add__(self, other): new_portfolio = Portfolio(f"{self.name}_{other.name}") new_portfolio.instruments = self.instruments + other.instruments new_portfolio.cash = self.cash + other.cash return new_portfolio def __str__(self): return f"Portfolio {self.name}: {len(self)} instruments, ${self.total_value():.2f}" def total_value(self): return self.cash + sum(inst.current_value() for inst in self.instruments)
8. FILE HANDLING – FINANCIAL DATA I/O
8.1 Reading CSV Files
import csv def read_stock_prices(filename): prices = [] with open(filename, 'r') as file: reader = csv.DictReader(file) for row in reader: prices.append({ 'symbol': row['Symbol'], 'date': row['Date'], 'close': float(row['Close']) }) return prices
8.2 Using pandas for Data Processing
import pandas as pd def load_financial_data(filename): df = pd.read_csv(filename, parse_dates=['Date']) df.set_index('Date', inplace=True) return df # Example usage data = load_financial_data('stock_data.csv') data['Returns'] = data['Close'].pct_change() data['Moving_Avg_50'] = data['Close'].rolling(window=50).mean()
8.3 Reading and Writing JSON
import json def save_portfolio_to_json(portfolio, filename): data = { 'name': portfolio.name, 'cash': portfolio.cash, 'instruments': [ {'symbol': inst.symbol, 'price': inst.price} for inst in portfolio.instruments ] } with open(filename, 'w') as f: json.dump(data, f, indent=4) def load_portfolio_from_json(filename): with open(filename, 'r') as f: data = json.load(f) # Parse and rebuild portfolio
8.4 Working with Excel Files
import pandas as pd def read_excel_financials(filename): df = pd.read_excel(filename, sheet_name='Financial_Statements') return df def write_excel_report(data, filename): with pd.ExcelWriter(filename) as writer: data.to_excel(writer, sheet_name='Portfolio_Analysis')
9. PRACTICAL IMPLEMENTATION
A. Building a Financial Calculator Class:
class FinancialCalculator: @staticmethod def future_value(pv, rate, periods): return pv * (1 + rate) ** periods @staticmethod def present_value(fv, rate, periods): return fv / (1 + rate) ** periods @staticmethod def annuity_pv(payment, rate, periods): return payment * (1 - (1 + rate) ** -periods) / rate @staticmethod def npv(cash_flows, discount_rate): return sum(cf / (1 + discount_rate) ** i for i, cf in enumerate(cash_flows)) @staticmethod def irr(cash_flows, guess=0.1): from scipy.optimize import brentq def npv(rate): return sum(cf / (1 + rate) ** i for i, cf in enumerate(cash_flows)) try: return brentq(npv, 0.0, 1.0) except ValueError: return None # Example usage calc = FinancialCalculator() print(f"Future Value of $1000 at 5% for 10 years: ${calc.future_value(1000, 0.05, 10):.2f}") print(f"NPV of [1000, -500, -500] at 10%: ${calc.npv([1000, -500, -500], 0.10):.2f}")
B. Portfolio Management Class:
class PortfolioManager: def __init__(self, risk_free_rate=0.02): self.risk_free_rate = risk_free_rate def calculate_returns(self, prices): return (prices[1:] / prices[:-1] - 1) def calculate_volatility(self, returns): return np.std(returns) def calculate_sharpe_ratio(self, returns): excess_returns = returns - self.risk_free_rate return np.mean(excess_returns) / np.std(excess_returns) def calculate_beta(self, asset_returns, market_returns): cov = np.cov(asset_returns, market_returns)[0, 1] var = np.var(market_returns) return cov / var def calculate_var(self, returns, confidence=0.95): return np.percentile(returns, (1 - confidence) * 100) def calculate_cvar(self, returns, confidence=0.95): var = self.calculate_var(returns, confidence) return np.mean(returns[returns <= var]) # Example usage pm = PortfolioManager() prices = np.array([100, 102, 98, 105, 110]) returns = pm.calculate_returns(prices) sharpe = pm.calculate_sharpe_ratio(returns) var_95 = pm.calculate_var(returns) print(f"Sharpe Ratio: {sharpe:.4f}") print(f"95% VaR: {var_95:.4f}")