Data Structures, Functions & Modules

Professional tools for banking, fintech & algorithmic trading

Python's built-in data structures — dictionaries, sets, and tuples — are the backbone of professional financial systems. Combined with well-designed functions and Python's powerful standard library, they power everything from real-time fraud detection to algorithmic trade execution.

Every example in this chapter is drawn from banking, fintech, and trading. You'll build progressively — simple account lookups grow into portfolio risk engines; basic functions become fraud-scoring pipelines.

Dictionaries — Account Management

Dictionaries are Python's most powerful built-in structure — every customer record, account balance, and portfolio in a financial system is a dict. Keys map to values with O(1) average lookup.

Why dicts dominate in finance: O(1) lookup by account ID, flexible nested schema, dict comprehensions for filtering and aggregation.

Essential methods: .get(key, default) (safe lookup), .items(), .update(), .pop(key)

Python
# Simple: safe account lookupaccounts = {    "ACC001": {"name": "Alice Chen",  "balance": 15_420.50, "tier": "silver"},    "ACC002": {"name": "Bob Patel",   "balance":  3_800.00, "tier": "bronze"},    "ACC003": {"name": "Carol Singh", "balance": 92_100.75, "tier": "gold"},}print(accounts.get("ACC001", {}).get("balance"))  # 15420.5print(accounts.get("ACC999"))                      # None — safe # Medium: comprehension + aggregationgold_only   = {k: v for k, v in accounts.items() if v["tier"] == "gold"}total_aum   = sum(v["balance"] for v in accounts.values())print(f"Gold : {list(gold_only.keys())}")print(f"AUM  : £{total_aum:,.2f}") # Complex: nested update — assign credit limitslimits = {"gold": 50_000, "silver": 10_000, "bronze": 2_000}for acc_id, info in accounts.items():    info["credit_limit"] = limits[info["tier"]]for acc_id, info in accounts.items():    print(f"{acc_id}  £{info['balance']:>10,.2f}  credit £{info['credit_limit']:>6,}")

Sets — Fraud Detection & Deduplication

Sets store unique elements with O(1) membership testing — ideal for blacklists, deduplication, and multi-source fraud correlation.

  • A & B — intersection (confirmed risk)
  • A | B — union (all suspicious)
  • A - B — difference (unique to one source)
  • A ^ B — symmetric difference

Speed: x in blacklist is O(1) for a set vs O(n) for a list.

Python
# Simple: blacklist screenblacklist   = {"ACC099", "ACC042", "ACC007"}incoming_tx = {"ACC001", "ACC002", "ACC099", "ACC042"}flagged = incoming_tx & blacklistsafe    = incoming_tx - blacklistprint(f"Flagged: {flagged}  Safe: {safe}") # Medium: deduplicationraw_ids = ["TX1001","TX1002","TX1001","TX1003","TX1002","TX1004"]unique  = set(raw_ids)print(f"Dupes removed: {len(raw_ids)-len(unique)}") # Complex: multi-bureau fraud correlationbureau_uk = {"ACC007", "ACC042", "ACC099", "ACC212"}bureau_eu = {"ACC042", "ACC099", "ACC200", "ACC315"}internal  = {"ACC099", "ACC500"}confirmed_high   = bureau_uk & bureau_eu & internalconfirmed_medium = ((bureau_uk & bureau_eu) | (bureau_uk & internal) |                    (bureau_eu & internal)) - confirmed_highprint(f"High risk  : {confirmed_high}")print(f"Medium risk: {confirmed_medium}")

Tuples & Immutability

Tuples are immutable sequences — once a trade executes, its record must never change. Immutability makes tuples ideal for financial records, audit logs, and fixed configs.

  • Tuple unpackingsymbol, price, qty = trade
  • Dict keys — tuples are hashable, so (symbol, date) can index price history
  • namedtuple — adds field names, making records self-documenting
Python
from collections import namedtuple # Simple: immutable trade recordtrade = ("AAPL", 182.50, 100, "BUY", "2024-01-15")symbol, price, qty, side, date = tradeprint(f"{side} {qty}x {symbol} @ £{price:.2f} — value £{price*qty:,.2f}")try:    trade[1] = 999except TypeError as e:    print(f"Cannot modify: {e}") # Medium: namedtuple for self-documenting recordsTrade = namedtuple("Trade", ["symbol", "price", "qty", "side", "date"])trades = [    Trade("TSLA", 210.00, 50, "SELL", "2024-01-15"),    Trade("MSFT", 415.25, 75, "BUY",  "2024-01-15"),]for t in sorted(trades, key=lambda t: t.price * t.qty, reverse=True):    print(f"{t.side:4} {t.qty}x {t.symbol}: £{t.price * t.qty:,.2f}") # Complex: tuple as composite dict keyprice_history = {("AAPL","2024-01-14"): 180.25, ("AAPL","2024-01-15"): 182.50}prev, curr = price_history[("AAPL","2024-01-14")], price_history[("AAPL","2024-01-15")]print(f"AAPL day return: {(curr/prev - 1)*100:+.2f}%")

Functions — Reusable Financial Logic

Functions are the building blocks of financial systems. Every business rule should live in a named, reusable, testable function.

Good function design: single responsibility, pure functions (same input → same output), return tuples for status/error pairs, docstrings documenting business rules.

Python
# Simple: compound interestdef compound_interest(principal, annual_rate, years, n=12):    """Returns (final_amount, interest_earned). n = periods/year."""    amount = principal * (1 + annual_rate / n) ** (n * years)    return round(amount, 2), round(amount - principal, 2) final, earned = compound_interest(10_000, 0.065, 5)print(f"£10,000 @ 6.5% for 5yr → £{final:,.2f}  (earned £{earned:,.2f})") # Medium: transfer validatordef validate_transfer(balance, amount, daily_limit, daily_spent=0):    if amount <= 0:         return False, "Amount must be positive"    if amount > balance:    return False, f"Insufficient funds (£{balance:,.2f})"    remaining = daily_limit - daily_spent    if amount > remaining:  return False, f"Daily limit (£{remaining:,.2f} left)"    return True, "Approved" for args in [(5000,1500,10000,0),(5000,6000,10000,0),(5000,1500,10000,9000)]:    ok, msg = validate_transfer(*args)    print(f"  {'✓'if ok else '✗'}  {msg}") # Complex: fraud risk scorerdef fraud_risk_score(amount, is_foreign, hour, past_flags=0):    score  = (30 if amount > 5000 else 15 if amount > 1000 else 0)    score += (25 if is_foreign else 0)    score += (20 if hour < 6 or hour > 22 else 0)    score += min(past_flags * 10, 25)    return min(score, 100) print(f"Normal daytime £500: {fraud_risk_score(500,False,14,0)}")print(f"Foreign £6k at 2am : {fraud_risk_score(6000,True,2,0)}")print(f"Repeat £2k         : {fraud_risk_score(2000,False,11,3)}")

*args & **kwargs — Flexible APIs

Financial APIs must handle variable inputs — a payment may have 1 or 10 metadata fields; a portfolio can hold any number of positions.

  • *args — extra positional args collected as a tuple
  • **kwargs — extra keyword args collected as a dict

Real-world use: payment processor APIs, trade order builders, batch-processing pipelines.

Python
# Simple: *args for variable portfolio positionsdef portfolio_value(*positions):    """positions: (price, qty) pairs — any number."""    return round(sum(p * q for p, q in positions), 2) print(f"£{portfolio_value((182.50,100),(210.00,50),(415.25,75)):,.2f}") # Medium: **kwargs for transaction metadatadef create_transaction(acc_id, amount, tx_type, **metadata):    return {"account": acc_id, "amount": amount, "type": tx_type, **metadata} tx = create_transaction("ACC001", 500.00, "transfer",                         reference="INV-001", channel="mobile")print(tx) # Complex: *args + **kwargs + defaultsdef process_batch(*account_ids, fee=0.0, notify=False, currency="GBP"):    results = [{"account": a, "fee": fee, "currency": currency,                "notified": notify} for a in account_ids]    return results, round(fee * len(account_ids), 2) records, total = process_batch("ACC001","ACC002","ACC003", fee=2.50, notify=True)print(f"Processed {len(records)}, total fees: £{total:.2f}")for r in records: print(f"  {r}")

Lambda & Higher-order Functions

Lambdas are anonymous single-expression functions — used as sort keys, filter criteria, and data transformations on financial datasets.

  • sorted(data, key=lambda x: ...) — rank positions by P&L or exposure
  • filter(lambda x: ..., data) — screen transactions or flag losers
  • map(lambda x: ..., data) — apply tax rates or transformations
Python
from collections import namedtuple Trade = namedtuple("Trade", ["symbol", "buy", "now", "qty"])portfolio = [    Trade("AAPL", 150.00, 182.50, 100), Trade("TSLA", 250.00, 210.00, 50),    Trade("MSFT", 380.00, 415.25, 75),  Trade("NVDA",  90.00, 485.00, 30),    Trade("META", 120.00, 374.00, 60),] # Simple: sort by P&Lpnl = lambda t: (t.now - t.buy) * t.qtyfor t in sorted(portfolio, key=pnl, reverse=True):    p = pnl(t)    print(f"{t.symbol:5} {'+'if p>=0 else ''}£{p:>9,.2f}") # Medium: filter gainers → map to CGTgainers = list(filter(lambda t: pnl(t) > 0, portfolio))cgt     = list(map(lambda t: (t.symbol, round(pnl(t)*0.20, 2)), gainers))print("\nCGT (20%):", cgt) # Complex: multi-key risk screenexposure = lambda t: t.now * t.qtypct_rtn  = lambda t: (t.now - t.buy) / t.buyranked   = sorted(portfolio, key=lambda t: (-exposure(t), pct_rtn(t)))print("\nRisk screen (exposure ↓, return ↑):")for t in ranked:    print(f"  {t.symbol:5} £{exposure(t):>9,.2f}  {pct_rtn(t)*100:>+6.1f}%")

Modules & the Standard Library

Python's standard library ships battle-tested modules critical for financial engineering:

  • decimal — exact base-10 arithmetic (0.1 + 0.2 in float = 0.30000000000000004)
  • datetime — trade timestamps, T+2 settlement, period calculations
  • collectionsCounter (transaction frequency), defaultdict, namedtuple
  • math — log returns, volatility, option pricing formulas
Python
from decimal import Decimal, ROUND_HALF_UPfrom datetime import date, timedeltafrom collections import Counterimport math # Decimal: exact moneyvalue = Decimal("182.50") * Decimal("100")fee   = (value * Decimal("0.001")).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)print(f"Trade: £{value}  Fee: £{fee}  Net: £{value-fee}")print(f"Float trap : 0.1+0.2 = {0.1+0.2}")print(f"Decimal fix: {Decimal('0.1')+Decimal('0.2')}") # datetime: T+2 settlementtrade_date = date(2024, 1, 15)print(f"\nSettlement: {trade_date + timedelta(days=2)}") # Counter: transaction frequencytx_log = ["purchase","transfer","purchase","atm","purchase","atm","refund"]print(f"Tx mix: {Counter(tx_log).most_common()}") # math: log returns & daily volatilityprices   = [100,102,101,105,103,108,112]log_rets = [math.log(prices[i]/prices[i-1]) for i in range(1,len(prices))]mean_r   = sum(log_rets)/len(log_rets)vol      = math.sqrt(sum((r-mean_r)**2 for r in log_rets)/len(log_rets))print(f"Daily vol: {vol*100:.3f}%")
PythonRuns entirely in your browser — nothing is sent to a server.

Practice Questions

Question 1

What does accounts.get('ACC999') return if 'ACC999' is not in the dict?

  • KeyError is raised
  • None
  • 0
  • An empty dict

Question 2

Which set operation returns accounts that appear in BOTH fraud bureau A AND bureau B?

  • A | B
  • A & B
  • A - B
  • A ^ B

Question 3

Why can tuples be used as dictionary keys but lists cannot?

  • Tuples are faster to iterate
  • Tuples are immutable and therefore hashable
  • Lists don't support the 'in' operator
  • Tuples always have a fixed size

Question 4

Given Trade = namedtuple('Trade', ['symbol','price']) and t = Trade('AAPL', 182.50), which access methods are valid?

  • Only t[1]
  • Only t.price
  • Both t[1] and t.price
  • t['price'] with a string key

Question 5

Inside def process(*amounts), what Python type is 'amounts'?

  • list
  • set
  • tuple
  • dict

Question 6

What does sorted(portfolio, key=lambda t: t.price * t.qty, reverse=True) sort by?

  • Alphabetically by symbol
  • Trade value (price × qty), highest first
  • Price only, highest first
  • The most recently added trade first

Question 7

Why use Decimal('0.1') + Decimal('0.2') instead of 0.1 + 0.2 in financial code?

  • Decimal is always faster for addition
  • Float has binary rounding errors: 0.1 + 0.2 = 0.30000000000000004, not 0.3
  • Float cannot represent decimal numbers
  • Decimal supports negative numbers; float does not

Question 8

What does list(filter(lambda t: pnl(t) > 0, portfolio)) return?

  • All trades sorted by P&L
  • Only trades where pnl(t) > 0 (winning positions)
  • The single trade with the highest P&L
  • True or False for each trade