This post takes you through the numerical libraries you will actually reach for in treasury and risk work, when each one earns its place, and how to set them up so they do not break your code halfway through a deadline.
Why Built in Python Is Not Enough
Python ships with a math module. It handles sine, cosine, logarithms, square roots. Fine for a single number. But you are not working with single numbers.
You are working with 250 cash flows across 30 years. You are testing 1,000 interest rate scenarios. You are running optimisation to find the cheapest hedge. You are running regressions on 20 years of daily prices. The built in math module will work. It will also be slow, clunky, and you will spend half your time writing loops that someone has already written better.
External numerical libraries solve this. They give you speed, they give you vectorised operations that let you work on entire datasets in one line, and they give you access to algorithms that would take weeks to code from scratch. They are how you move from scripting to production.
The Libraries You Will Actually Use
NumPy: Arrays and Speed
NumPy is the foundation. Everything else in the numerical Python stack rests on it.
NumPy gives you arrays: objects that hold many numbers and let you operate on all of them at once without writing a loop. It gives you matrix operations. It gives you speed, because NumPy arrays are implemented in C and run far faster than Python lists.
A simple example. You have 10,000 interest rates and you need to add 0.5% to all of them (0.005 expressed as a decimal).
import numpy as np
# The NumPy way
rates = np.array([0.03, 0.035, 0.04, 0.045, 0.05])
adjusted_rates = rates + 0.005
print(adjusted_rates)
# Output: [0.035 0.04 0.045 0.05 0.055]
That is one line. One operation. NumPy applies it to every element. No loop. No temporary variable. No fussing.
Without NumPy you write:
rates = [0.03, 0.035, 0.04, 0.045, 0.05]
adjusted_rates = []
for rate in rates:
adjusted_rates.append(rate + 0.005)
Same result. Three lines instead of one. More room for mistakes. Slower on large datasets.
NumPy is also where you do linear algebra. Multiplying matrices, solving systems of equations, finding eigenvalues. These are common in portfolio optimisation, factor models, and term structure modelling.
You will use NumPy because everything else depends on it. Even if you never type import numpy explicitly, you are using it when you use pandas or scipy.
SciPy: Advanced Maths and Optimisation
SciPy sits on top of NumPy. It provides statistical functions, optimisation, interpolation, and signal processing.
In treasury and risk work, you reach for SciPy for:
Statistical testing. You have a hypothesis about basis behaviour, roll behaviour, or liquidity spreads. SciPy has t tests, chi squared tests, correlation functions. You can run them on your data without coding the maths yourself.
Optimisation. You need to find the portfolio weights that minimise variance subject to constraints. You need to calibrate a curve to market prices. You need to find the optimal hedge ratio. SciPy's optimize module does this.
Interpolation. You have yield curve points at 2y, 5y, 10y. You need rates at 3y and 7y. SciPy interpolates between them.
A simple optimisation example. You want to find the rate that minimises squared error against market data.
from scipy.optimize import minimize
import numpy as np
# Market prices at different rates
rates = np.array([0.02, 0.03, 0.04, 0.05])
prices = np.array([101.5, 100.2, 99.0, 97.8])
# We want to fit a bond price model
def price_model(rate):
return 100 / (1 + rate) ** 5
# Objective: minimise squared error
def objective(rate):
model_prices = np.array([price_model(r) for r in rates])
error = np.sum((prices - model_prices) ** 2)
return error
# Optimise
result = minimize(objective, x0=0.03)
optimal_rate = result.x[0]
print(f"Optimal rate: {optimal_rate:.4f}")
SciPy solves this. You define the objective function. SciPy finds the input that minimises it. You do not need to know the calculus. You do not need to write gradient descent. SciPy handles it.
Pandas: Data as Tables
NumPy works with arrays. Pandas works with tables: rows, columns, labels, the things a treasury analyst thinks about every day.
Pandas gives you the DataFrame: a table where you can refer to columns by name, filter by condition, group by criteria, and pivot. It is where raw data becomes usable.
In practice, most of your data arrives as a CSV or an Excel file. NumPy does not want to know about it. Pandas reads it, cleans it, shapes it, and hands it to NumPy or SciPy when you are ready to analyse.
A weekly note on treasury, liquidity and practical Python. No spam, unsubscribe any time.
import pandas as pd
# Read data
df = pd.read_csv('cash_flows.csv')
# Filter to a maturity bucket
bucket = df[df['maturity_years'] <= 5]
# Group by currency and sum
by_currency = bucket.groupby('currency')['amount'].sum()
print(by_currency)
You are not writing SQL. You are not pivoting in Excel by hand. Pandas does it programmatically, repeatably, at scale.
Matplotlib and Seaborn: Plotting
Matplotlib is the standard plotting library. Seaborn wraps it with defaults that look better and cut boilerplate.
You use these to visualise your results: yield curves, liquidity forecasts, risk dashboards. Matplotlib is more flexible. Seaborn is faster for statistical plots.
For most finance work, Matplotlib is enough. When you want a polished statistical plot quickly, reach for Seaborn.
import matplotlib.pyplot as plt
import numpy as np
# Simple yield curve
maturities = np.array([1, 2, 3, 5, 7, 10])
rates = np.array([0.02, 0.025, 0.03, 0.035, 0.04, 0.042])
plt.plot(maturities, rates, marker='o')
plt.xlabel('Maturity (years)')
plt.ylabel('Rate')
plt.title('Yield Curve')
plt.grid(True)
plt.show()
Three lines of actual work. You get a professional plot.
Installing Libraries the Right Way
This seems obvious but it is where people get stuck.
Use pip, the package installer for Python. From your terminal or command prompt:
pip install numpy scipy pandas matplotlib seaborn
For a specific version (important if you are working in a team or a controlled environment):
pip install numpy==1.24.3 scipy==1.11.2
Create a requirements.txt file in your project directory and list your dependencies:
numpy==1.24.3
scipy==1.11.2
pandas==2.0.3
matplotlib==3.7.2
seaborn==0.12.2
Then anyone can run:
pip install -r requirements.txt
And get exactly the same versions you have.
Virtual environments prevent library conflicts between projects. Before installing anything, run python -m venv venv to create one, then source venv/bin/activate on Mac/Linux or venv\Scripts\activate on Windows. Each project lives in its own isolated environment. This saves hours of debugging when library versions clash.
A Worked Example: Interest Rate Scenarios
Say you are a treasury analyst. You have a portfolio of loans and deposits with different maturities and rates. Interest rates move. You want to know how much money you make or lose.
You build 100 interest rate scenarios. For each one, you recalculate the mark to market value of every position. You want the distribution of outcomes: worst case, best case, what is most likely.
Here is how you actually do it.
import numpy as np
import pandas as pd
from scipy import stats
# Portfolio: rate, maturity, amount
positions = {
'instrument': ['loan_A', 'loan_B', 'deposit_A', 'deposit_B'],
'rate': [0.035, 0.040, 0.020, 0.025],
'maturity': [5, 10, 3, 7],
'amount': [1000000, 2000000, 500000, 1500000]
}
df = pd.DataFrame(positions)
# Generate 100 rate scenarios (simple: +/- 2% normally distributed)
# 0.01 is 1% standard deviation
num_scenarios = 100
rate_shocks = np.random.normal(0, 0.01, num_scenarios)
# For each scenario, recalculate PV
mtm_values = []
for shock in rate_shocks:
# Adjusted rates
adjusted_rates = df['rate'] + shock
# PV of each position (simplified: PV = amount / (1 + rate) ^ maturity)
pv = df['amount'] / ((1 + adjusted_rates) ** df['maturity'])
# Total mark to market for this scenario
total_mtm = pv.sum()
mtm_values.append(total_mtm)
mtm_array = np.array(mtm_values)
# Analyse results
print(f"Mean MTM: £{mtm_array.mean():,.0f}")
print(f"Std Dev: £{mtm_array.std():,.0f}")
print(f"5th percentile (VaR): £{np.percentile(mtm_array, 5):,.0f}")
print(f"95th percentile: £{np.percentile(mtm_array, 95):,.0f}")
NumPy handles the array operations. Pandas keeps your data tidy. You get results in seconds. No loops written by hand. No temporary arrays. Clean, readable, repeatable.
How to Choose the Right Library
It is a pragmatic trade off each time.
Speed versus readability. NumPy is faster than Pandas for numerical work, but Pandas is more readable when you are working with labelled data. On a portfolio of 10,000 positions, you want the readability. On intraday tick data with 100 million points, you might need the speed.
Generalised tools versus specialist libraries. SciPy has optimisation. So does scikit.learn. SciPy is lighter weight and more focused on numerical work. scikit.learn is heavier but gives you more machine learning algorithms. If you are doing a one off optimisation, SciPy. If you are building a predictive model, scikit.learn.
Standard dependencies versus niche packages. NumPy, SciPy, Pandas, Matplotlib: everyone has them. No questions asked. There are specialist finance libraries like QuantLib or Zipline. They are powerful and domain specific. But they add dependency risk. Your code breaks when they break. Prefer standard libraries unless the specialist library saves you weeks of work.
In house standards versus what you need. If your team uses SciPy everywhere, you use SciPy. Consistency beats perfection. If you are starting fresh, pick the library that makes your code simplest and most correct.
Summary: The Core Four Libraries
You now have the map. NumPy for speed and arrays. SciPy for statistical tests and optimisation. Pandas for data as tables. Matplotlib for plots. These four cover the vast majority of treasury and risk work.
Everything else either builds on these or serves a specialist purpose. Start here. Master the core four before you reach for anything else.
What Comes Next
Your next step is to use them on real portfolios and real data. That is where the skill lives.
The libraries work together. Pandas feeds data to NumPy. NumPy feeds arrays to SciPy. SciPy feeds results to Matplotlib. Learn to move data between them without friction. That competence is what distinguishes a scripter from a production developer.

The Complete Python Course
Welcome to the most practical and beginner friendly Python Bootcamp Course on YouTube.
Take the courseGet the next one in your inbox
A weekly note across Finance & Treasury, Innovation & Automation and Career Development. No spam, unsubscribe any time.
Notes across finance and treasury, innovation and automation, and career development, written by practitioners who do the work.
