APIs, Regex & Functional Python
REST clients, data validation, higher-order functions, and concurrency
Every production Python system in finance touches external data: market prices from Bloomberg or Refinitiv, order confirmations from broker APIs, reference data from internal microservices. This chapter teaches you to consume REST APIs professionally — authentication, error recovery, rate limiting, and session management — the same patterns used in trading desks and fintech platforms worldwide.
Regular expressions are the standard tool for validating and extracting structured data from unstructured text. You will write patterns to validate ISINs, BIC codes, trade references, and monetary amounts — and build a reusable validator that can be dropped into any data ingestion pipeline.
Then functional programming: map, filter, and reduce compose clean, testable data-transformation pipelines without mutation or loops. Finally, Python's concurrency model — the GIL, when threading beats multiprocessing, and the concurrent.futures interface that unifies both — equips you to write systems that fetch data from dozens of APIs simultaneously or parallelise risk calculations across CPU cores.
Consuming REST APIs — Market Data & Order Management
The requests library is the industry standard for HTTP in Python. Every major financial data provider — Bloomberg, Refinitiv, Alpha Vantage, Alpaca, Interactive Brokers — exposes a REST API, and requests is how you talk to them.
- GET — read data: price quotes, positions, account balances
- POST — write data: place orders, submit confirmations
- Headers — pass authentication:
Authorization: Bearer <token> - Timeout — always set one; a hanging request blocks the thread indefinitely
response.raise_for_status()— raisesHTTPErroron 4xx/5xx; never ignore status codes
Why it matters: In live trading, a missed timeout or unchecked 401 can leave an order unacknowledged. Production systems treat every API call as potentially failing and code accordingly.
import requests API_BASE = "https://api.marketdata.example.com/v1"API_KEY = "your-api-key-here" HEADERS = { "Authorization": f"Bearer {API_KEY}", "Accept": "application/json", "X-Client-Id": "trading-desk-py",} def get_quotes(symbols: list[str]) -> list[dict]: """ Fetch real-time quotes for a list of ticker symbols. Returns a list of quote dicts; raises on HTTP errors. """ url = f"{API_BASE}/quotes" params = {"symbols": ",".join(symbols)} response = requests.get( url, headers=HEADERS, params=params, timeout=(3.05, 10), # (connect timeout, read timeout) — always set both ) response.raise_for_status() # raises HTTPError on 4xx / 5xx data = response.json() # parse JSON body return data["quotes"] # extract the list we care about def place_order(symbol: str, side: str, qty: int, order_type: str = "MARKET") -> dict: """ Submit an equity order via POST. Returns the broker's order acknowledgement. """ payload = { "symbol": symbol, "side": side, # "BUY" | "SELL" "quantity": qty, "order_type": order_type, } response = requests.post( f"{API_BASE}/orders", headers=HEADERS, json=payload, # sets Content-Type: application/json automatically timeout=(3.05, 10), ) response.raise_for_status() return response.json() # ── Usage ─────────────────────────────────────────────────────────────────────# quotes = get_quotes(["AAPL", "MSFT", "NVDA", "TSLA"])# for q in quotes:# print(f"{q['symbol']:<6} bid={q['bid']:>8.2f} ask={q['ask']:>8.2f}") # ack = place_order("AAPL", "BUY", 100)# print(f"Order {ack['order_id']} accepted — status: {ack['status']}")Robust API Clients — Retries, Rate Limits & Sessions
Production API clients must handle three failure modes gracefully: transient network errors, server overload (429), and authentication expiry. A bare requests.get() call handles none of them.
requests.Session— reuses the underlying TCP connection; significant latency saving for high-frequency pollingurllib3.Retry+HTTPAdapter— automatic retries with exponential backoff; mount on the session- 429 Too Many Requests — the server's rate-limit signal; respect the
Retry-Afterheader - Never retry on 400 (bad request) or 401 (auth failure) — those are your bugs, not transient
- Context manager — wrap the session in
withso the connection pool is always released
Best practice: wrap every external API call in a client class. It centralises auth, retry logic, and base-URL configuration — making it trivial to swap providers or mock in tests.
import timeimport requestsfrom requests.adapters import HTTPAdapterfrom urllib3.util.retry import Retry class MarketDataClient: """ Production-grade API client with connection reuse, automatic retries, and rate-limit handling. One instance per application — not per request. """ _RETRY_STRATEGY = Retry( total=3, backoff_factor=0.5, # waits 0.5s, 1s, 2s between retries status_forcelist=[500, 502, 503, 504], # retry on server errors only allowed_methods=["GET"], # never auto-retry POST (not idempotent) raise_on_status=False, ) def __init__(self, base_url: str, api_key: str): self.base_url = base_url.rstrip("/") self._session = requests.Session() self._session.headers.update({ "Authorization": f"Bearer {api_key}", "Accept": "application/json", }) adapter = HTTPAdapter(max_retries=self._RETRY_STRATEGY) self._session.mount("https://", adapter) self._session.mount("http://", adapter) def __enter__(self): return self def __exit__(self, *_): self._session.close() def get(self, path: str, **kwargs) -> dict: url = f"{self.base_url}/{path.lstrip('/')}" resp = self._session.get(url, timeout=(3.05, 10), **kwargs) if resp.status_code == 429: wait = int(resp.headers.get("Retry-After", 60)) print(f"[rate-limit] sleeping {wait}s") time.sleep(wait) resp = self._session.get(url, timeout=(3.05, 10), **kwargs) resp.raise_for_status() return resp.json() # ── Usage ─────────────────────────────────────────────────────────────────────# with MarketDataClient("https://api.example.com/v1", api_key="...") as client:# quotes = client.get("/quotes", params={"symbols": "AAPL,MSFT"})# history = client.get("/history/AAPL", params={"days": 30})Regular Expressions — Core Patterns
The re module gives you a mini-language for describing text patterns. In financial systems, regex is the standard tool for parsing Bloomberg terminal output, validating reference data, and extracting fields from unstructured trade confirmations.
re.match(pattern, s)— matches only at the start of the string; returns a Match object or Nonere.search(pattern, s)— finds the first match anywhere in the stringre.findall(pattern, s)— returns a list of all non-overlapping matchesre.sub(pattern, repl, s)— replace every match withreplre.compile(pattern)— pre-compiles for reuse; essential in loops over millions of records
Key syntax: . any char · \d digit · \w word char · [A-Z] character class · {n,m} quantifier · ^/$ anchors · () capture group
Best practice: always re.compile() patterns that are used more than once. The compiled regex is cached by the interpreter, but explicit compilation makes intent clear and avoids accidental recompilation inside hot loops.
import re # ── 1. re.compile — pre-compile for reuse ────────────────────────────────────CURRENCY_CODE = re.compile(r"[A-Z]{3}") # ISO 4217 currency code line = "FX spot: USD/GBP 0.7852 EUR/USD 1.0834 JPY/USD 0.0067"currencies = CURRENCY_CODE.findall(line)print("Currencies found:", currencies) # ['USD', 'GBP', 'EUR', 'USD', 'JPY', 'USD'] # ── 2. re.match vs re.search ──────────────────────────────────────────────────TICKER = re.compile(r"[A-Z]{1,5}") print(re.match(r"[A-Z]{1,5}", "AAPL.L")) # Match — starts with uppercaseprint(re.match(r"[A-Z]{1,5}", "Price: AAPL")) # None — doesn't start with itprint(re.search(r"[A-Z]{1,5}", "Price: AAPL"))# Match — found at offset 7 # ── 3. re.findall with groups — extract bid/ask pairs ────────────────────────QUOTE_PATTERN = re.compile(r"([A-Z]{3,4}):s*([d.]+)/([d.]+)")feed = "EURUSD: 1.0832/1.0835 GBPUSD: 1.2701/1.2704 USDJPY: 149.82/149.85" for m in QUOTE_PATTERN.finditer(feed): pair, bid, ask = m.group(1), m.group(2), m.group(3) print(f"{pair} bid={bid} ask={ask}") # ── 4. re.sub — normalise noisy Bloomberg ticker format ──────────────────────SUFFIX = re.compile(r"s+(Equity|Corp|Govt|Index)$", re.IGNORECASE)raw_tickers = ["AAPL US Equity", "BP/ LN Equity", "T 4.5 01/15/2030 Corp"]clean = [SUFFIX.sub("", t).strip() for t in raw_tickers]print("Cleaned tickers:", clean) # ['AAPL US', 'BP/ LN', 'T 4.5 01/15/2030']Regex for Financial Data Validation
Financial systems live and die on reference data quality. A single malformed ISIN, invalid BIC, or bad trade ID can fail a settlement instruction and trigger a financial penalty. Regex validation at the point of ingestion — before the data reaches the database — is standard practice.
- ISIN — 12 chars: 2-letter country code + 9 alphanumeric + 1 check digit:
^[A-Z]{2}[A-Z0-9]{9}[0-9]$ - BIC/SWIFT — 8 or 11 chars: 4-letter bank + 2-letter country + 2-char location + optional 3-char branch:
^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$ - Trade reference — firm-specific; typically prefix + date + sequence:
^TRD-\d{8}-\d{6}$ - Monetary amount — optional currency + digits + optional decimal:
^[A-Z]{0,3}\s*\d{1,15}(\.\d{1,4})?$
Production pattern: build a DataValidator class with a registry of compiled patterns. Add a validate(field, value) method that returns (is_valid, reason). This makes validation rules explicit, testable, and swappable without touching business logic.
import re class DataValidator: """ Registry of compiled regex patterns for financial reference data. Returns (True, None) on success or (False, reason) on failure. Use in data-ingestion pipelines before writing to the database. """ _PATTERNS = { "isin": re.compile(r"^[A-Z]{2}[A-Z0-9]{9}[0-9]$"), "bic": re.compile(r"^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$"), "trade_ref": re.compile(r"^TRD-d{8}-d{6}$"), "ticker": re.compile(r"^[A-Z]{1,5}$"), "currency": re.compile(r"^[A-Z]{3}$"), "amount": re.compile(r"^d{1,15}(.d{1,4})?$"), } @classmethod def validate(cls, field: str, value: str) -> tuple[bool, str | None]: pattern = cls._PATTERNS.get(field) if pattern is None: return False, f"Unknown field type: {field}" if pattern.match(value): return True, None return False, f"'{value}' is not a valid {field}" @classmethod def validate_trade(cls, trade: dict) -> list[str]: """Validate a full trade dict; return list of error strings.""" checks = [ ("isin", trade.get("isin", "")), ("bic", trade.get("counterparty_bic", "")), ("trade_ref", trade.get("ref", "")), ("currency", trade.get("ccy", "")), ("amount", str(trade.get("notional", ""))), ] errors = [] for field, value in checks: ok, msg = cls.validate(field, value) if not ok: errors.append(msg) return errors # ── Usage ─────────────────────────────────────────────────────────────────────trades = [ {"ref": "TRD-20240315-000001", "isin": "GB00B15KXQ89", "ccy": "GBP", "notional": "250000.00", "counterparty_bic": "BARCGB22"}, {"ref": "TRD-BAD-REF", "isin": "US0378331005", "ccy": "USD", "notional": "175000.00", "counterparty_bic": "CHAS"}, # bad ref + bad BIC] for t in trades: errors = DataValidator.validate_trade(t) status = "PASS" if not errors else "FAIL" print(f"{t['ref']:<28} {status}") for e in errors: print(f" -> {e}")Functional Programming — map, filter, reduce
Functional programming centres on pure functions — functions that produce the same output for the same input and have no side effects. Applied to collections, three higher-order functions cover most transformation needs.
map(fn, iterable)— applyfnto every element; returns a lazy iterator. Use when you want to transform every record in a stream without materialising the whole list in memoryfilter(pred, iterable)— keep only elements wherepred(element)is True; also lazy. Use to screen out invalid records before expensive processingfunctools.reduce(fn, iterable)— fold the sequence into a single value left-to-right. Use for aggregations: total P&L, sum of notionals, running max
map/filter vs list comprehensions:
- Comprehension
[f(x) for x in xs if pred(x)]— preferred for readability in most cases map/filter— preferred for lazy pipelines on large datasets, or when chaining with other higher-order functions
Best practice: name your transformation functions rather than writing complex lambdas inline. map(mark_to_market, positions) reads like documentation; map(lambda p: {**p, "pnl": (p["price"] - p["avg"]) * p["qty"]}, positions) does not.
from functools import reduce # ── Portfolio positions ────────────────────────────────────────────────────────positions = [ {"sym": "AAPL", "qty": 100, "avg_price": 172.50, "current_price": 186.55}, {"sym": "MSFT", "qty": 50, "avg_price": 380.00, "current_price": 415.30}, {"sym": "TSLA", "qty": 40, "avg_price": 225.00, "current_price": 195.40}, {"sym": "NVDA", "qty": 30, "avg_price": 480.00, "current_price": 520.00}, {"sym": "META", "qty": 25, "avg_price": 510.00, "current_price": 487.20},] # ── 1. map — mark every position to market ────────────────────────────────────def mark_to_market(pos: dict) -> dict: pnl = round((pos["current_price"] - pos["avg_price"]) * pos["qty"], 2) pnl_pct = round((pos["current_price"] / pos["avg_price"] - 1) * 100, 2) return {**pos, "pnl": pnl, "pnl_pct": pnl_pct} marked = list(map(mark_to_market, positions)) # ── 2. filter — isolate losing positions > 2% down ───────────────────────────losers = list(filter(lambda p: p["pnl_pct"] < -2.0, marked)) # ── 3. reduce — aggregate total portfolio P&L ────────────────────────────────total_pnl = reduce(lambda acc, p: acc + p["pnl"], marked, 0.0) # ── 4. Print results ──────────────────────────────────────────────────────────print(f"{'Symbol':<6} {'Qty':>5} {'Avg':>8} {'Price':>8} {'P&L':>10} {'%':>7}")print("-" * 50)for p in marked: sign = "+" if p["pnl"] >= 0 else "" print(f"{p['sym']:<6} {p['qty']:>5} {p['avg_price']:>8.2f} " f"{p['current_price']:>8.2f} {sign}{p['pnl']:>9,.2f} {sign}{p['pnl_pct']:>6.2f}%")print("-" * 50)sign = "+" if total_pnl >= 0 else ""print(f"{'TOTAL P&L':>42} {sign}{total_pnl:>9,.2f}") print()print("Positions down > 2%:")for p in losers: print(f" {p['sym']}: {p['pnl_pct']:.2f}% ({p['pnl']:,.2f})")Concurrency — GIL, Threading & Multiprocessing
The Global Interpreter Lock (GIL) is a mutex inside CPython that ensures only one thread executes Python bytecode at a time. It protects CPython's reference-counting memory manager from corruption. The GIL releases in two situations: (1) after every ~5ms (configurable via sys.setswitchinterval()), giving other threads a chance to run; (2) whenever a thread makes a blocking system call — network I/O, file I/O, time.sleep. This means threading is effective for I/O-bound work but provides zero CPU parallelism. Python 3.13 introduced an experimental --disable-gil mode, but it is not yet production-ready.
Three concurrency models — choose by task type:
- threading — multiple threads in one process, shared memory, GIL applies. Best for I/O-bound tasks (API calls, DB queries, file I/O). Low overhead (~8KB per thread). Use
threading.Threadfor direct control orThreadPoolExecutorfor managed pools. - multiprocessing — multiple OS processes, each with its own GIL and memory space. Best for CPU-bound tasks (Monte Carlo, backtesting, numerical computation). Higher overhead (~50MB per process). Use
ProcessPoolExecutorfor managed pools. - asyncio — single thread, cooperative (coroutine-based) concurrency. No GIL concerns. Best for very high-volume I/O (thousands of simultaneous connections, WebSocket streams). Requires the full call stack to use async-aware libraries (
aiohttp,asyncpg).
threading.Thread — direct thread control:
t = threading.Thread(target=fn, args=(a, b))— creates a threadt.start()— launches the thread; returns immediatelyt.join()— blocks the caller until threadtfinishest.daemon = True— mark as daemon: thread dies when main program exits (useful for background monitors)- Best practice: always
.join()non-daemon threads before program exit to avoid lost work
Thread safety — race conditions and synchronisation: When multiple threads read-modify-write shared state simultaneously, the result is non-deterministic. Python's += is not atomic — it compiles to multiple bytecodes and another thread can interrupt mid-operation.
threading.Lock()— mutual exclusion. Use as a context manager:with lock: .... Only one thread can hold the lock at a time; others block until it is released.threading.RLock()— reentrant lock; the same thread can acquire it multiple times without deadlocking.threading.Queue— thread-safe FIFO queue. The standard tool for producer-consumer pipelines between threads.queue.put(item)/queue.get()are both thread-safe. Prefer Queue over Lock+list for passing data between threads.- Best practice: minimise shared mutable state. Prefer passing results back through a Queue or by returning from
future.result()rather than writing to shared variables with locks.
concurrent.futures — the modern unified interface:
ThreadPoolExecutor(max_workers=N)— reusable thread pool; threads are created once and reused across tasksProcessPoolExecutor(max_workers=N)— reusable process pool. Useos.cpu_count()as the default for CPU-bound workexecutor.submit(fn, *args)— schedulesfn(*args)and returns aFutureimmediatelyexecutor.map(fn, iterable)— like built-inmapbut parallel; yields results in orderas_completed(futures)— yieldsFutureobjects as they complete (fastest-first, not submission order)future.result()— blocks until done and returns the value, or re-raises any exception from the worker- Use as context manager (
with executor:) to ensure clean shutdown and thread/process cleanup
asyncio — cooperative concurrency:
async def fn():— defines a coroutine. Calling it returns a coroutine object (not a result); must be awaited or gatheredawait expr— suspends the current coroutine and yields control to the event loop untilexprcompletesasyncio.gather(*coroutines)— runs multiple coroutines concurrently in the event loop; returns all results when all completeasyncio.run(main())— creates an event loop, runs the coroutine, closes the loop (use this as the entry point)- Key constraint: never call blocking functions (
time.sleep,requests.get) inside an async function — they block the event loop and defeat the purpose. Useawait asyncio.sleep()and async libraries likeaiohttpinstead
Production decision table:
- Parallel API calls (10–100 endpoints) →
ThreadPoolExecutor - Parallel DB queries across microservices →
ThreadPoolExecutor - Monte Carlo VaR, backtesting, scenario analysis →
ProcessPoolExecutor - NumPy/Pandas matrix operations → NumPy/C extensions (release GIL internally)
- 10,000+ simultaneous connections, WebSocket streams →
asyncio+aiohttp - Producer-consumer pipeline between threads →
threading.Queue - Background monitoring / heartbeat → daemon
Thread
import time, threading, randomfrom concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed EXCHANGES = ["NYSE", "LSE", "XETRA", "TSE", "ASX"]PRICES = {"NYSE":186.55,"LSE":147.30,"XETRA":152.40,"TSE":143.90,"ASX":135.00} # ─────────────────────────────────────────────────────────────────# PART 1 — threading.Thread: direct lifecycle control# ─────────────────────────────────────────────────────────────────quotes = [] def fetch_quote(exchange): time.sleep(0.1) # simulate 100ms network latency quotes.append({"exchange": exchange, "price": PRICES[exchange]}) threads = [threading.Thread(target=fetch_quote, args=(ex,)) for ex in EXCHANGES]t0 = time.time()for t in threads: t.start() # launch all 5 threadsfor t in threads: t.join() # wait for all to finishelapsed = round((time.time() - t0) * 1000)print(f"=== threading.Thread: 5 exchanges in {elapsed}ms (vs ~500ms sequential) ===")for r in sorted(quotes, key=lambda x: x["exchange"]): print(f" {r['exchange']:<8} {r['price']:>8.2f}") # ─────────────────────────────────────────────────────────────────# PART 2 — Thread safety: Lock prevents race conditions# ─────────────────────────────────────────────────────────────────total_notional = 0.0lock = threading.Lock() def book_trade(qty, price): """Without lock, concurrent += on total_notional is non-deterministic.""" global total_notional notional = qty * price time.sleep(0.005) with lock: # only one thread enters this block at a time total_notional += notional trades = [(100,186.55),(50,415.30),(30,520.00),(20,195.40),(25,487.20)]workers = [threading.Thread(target=book_trade, args=t) for t in trades]for w in workers: w.start()for w in workers: w.join()expected = sum(q * p for q, p in trades)print(f"\n=== Lock: total notional = {total_notional:,.2f} (expected {expected:,.2f}) ===") # ─────────────────────────────────────────────────────────────────# PART 3 — threading.Queue: thread-safe producer-consumer pipeline# ─────────────────────────────────────────────────────────────────import queue as queue_mod trade_queue = queue_mod.Queue()validated = []errors_q = [] def producer(): """Generates trade records and puts them onto the queue.""" raw_trades = [ {"id":"T001","sym":"AAPL","qty":100,"price":186.55}, {"id":"T002","sym":"MSFT","qty": 50,"price":415.30}, {"id":"T003","sym":"????","qty": -5,"price":520.00}, # invalid {"id":"T004","sym":"NVDA","qty": 30,"price":520.00}, ] for t in raw_trades: trade_queue.put(t) trade_queue.put(None) # sentinel — signals consumer to stop def consumer(): """Validates each trade taken from the queue.""" while True: item = trade_queue.get() if item is None: break if item["qty"] > 0 and item["sym"].isalpha(): validated.append(item) else: errors_q.append(item["id"]) trade_queue.task_done() p = threading.Thread(target=producer)c = threading.Thread(target=consumer)p.start(); c.start()p.join(); c.join()print(f"\n=== Queue pipeline: {len(validated)} valid, {len(errors_q)} rejected ({errors_q}) ===") # ─────────────────────────────────────────────────────────────────# PART 4 — ThreadPoolExecutor: recommended pool for I/O tasks# ─────────────────────────────────────────────────────────────────SERVICES = { "price-svc": (200, {"AAPL":186.55,"MSFT":415.30}), "risk-svc": (200, {"AAPL":0.25, "MSFT":0.18 }), "fx-svc": (200, {"GBPUSD":1.27,"EURUSD":1.09}), "news-svc": (429, None), # rate-limited "ref-data-svc": (200, {"AAPL":"US0378331005"}),} def call_service(name): time.sleep(0.08) status, data = SERVICES[name] if status != 200: raise RuntimeError(f"HTTP {status}") return name, data t0 = time.time()svc_results = {}print("\n=== ThreadPoolExecutor: 5 microservices ===")with ThreadPoolExecutor(max_workers=5) as pool: future_map = {pool.submit(call_service, svc): svc for svc in SERVICES} for future in as_completed(future_map): svc = future_map[future] try: name, data = future.result() # re-raises worker exception if any svc_results[name] = data print(f" OK {name}") except Exception as e: print(f" ERR {svc}: {e}")elapsed = round((time.time() - t0) * 1000)print(f"All 5 services queried in {elapsed}ms") # ─────────────────────────────────────────────────────────────────# PART 5 — ProcessPoolExecutor: CPU-bound Monte Carlo VaR# Bypasses GIL: each worker is a separate OS process# ─────────────────────────────────────────────────────────────────def mc_path(seed): """One simulation path — pure CPU, no I/O, no GIL release.""" random.seed(seed) v = 1_000_000.0 for _ in range(5_000): v *= 1 + random.gauss(0.0005, 0.015) return v # ThreadPoolExecutor would NOT speed this up — GIL blocks parallel Python bytecodewith ProcessPoolExecutor(max_workers=4) as pool: paths = sorted(pool.map(mc_path, range(200))) var_95 = 1_000_000 - paths[int(0.05 * len(paths))]print(f"\n=== ProcessPoolExecutor: Monte Carlo VaR (200 paths, 4 cores) ===")print(f" 95% 1-day VaR : ${var_95:,.0f}")print(f" Min portfolio : ${paths[0]:,.0f} Max: ${paths[-1]:,.0f}") # ─────────────────────────────────────────────────────────────────# PART 6 — asyncio: cooperative concurrency (single-threaded, GIL-irrelevant)# ─────────────────────────────────────────────────────────────────import asyncio async def async_fetch(symbol, latency_ms=80): """Simulates async HTTP call — yields control during I/O wait.""" await asyncio.sleep(latency_ms / 1000) # non-blocking wait prices = {"AAPL":186.55,"MSFT":415.30,"NVDA":520.00,"AMZN":175.50} return symbol, prices[symbol] async def fetch_all(): t0 = time.time() # asyncio.gather runs all coroutines concurrently in the event loop results = await asyncio.gather( async_fetch("AAPL"), async_fetch("MSFT"), async_fetch("NVDA"), async_fetch("AMZN"), ) elapsed = round((time.time() - t0) * 1000) print(f"\n=== asyncio.gather: 4 async fetches in {elapsed}ms ===") for sym, price in results: print(f" {sym:<5} {price:>8.2f}") asyncio.run(fetch_all())Practice Questions
Question 1
What does re.match("GBP", "Rate: GBP 1.25") return?
Question 2
What does map(float, ["183.50", "415.20", "520.00"]) return in Python 3?
Question 3
A risk system runs Monte Carlo simulations using 4 CPU cores. Which concurrency tool will actually achieve parallel execution despite the GIL?
Question 4
How many characters does a valid ISIN contain, and what is the structure?
Question 5
What does reduce(lambda a, b: a + b, [10_000, 25_000, 15_000]) return?
Question 6
A market data API returns HTTP 429. What is the correct production response?
Question 7
What does list(filter(None, [0, 1, "", "AAPL", False, 42, None])) return?
Question 8
Why is threading effective for parallelising API calls to 10 exchanges simultaneously, despite the GIL?