N
Noah Sheldon
BlogProjectsSkillsExperienceLinks
free ai audit
Home/Blog/High Level Design/Scaling Trading Systems at BNP Paribas: Lessons in Financial Technology
Deep DiveHigh Level DesignFintechArchitecture

Scaling Trading Systems at BNP Paribas: Lessons in Financial Technology

Deep dive into building resilient post-trade systems that handle millions in daily transactions.

Jul 19, 2026
15 min

Trading systems operate at a level of reliability most software never has to reach. A social media app can go down for five minutes and users will refresh. A trading platform that fails for five seconds can mean millions in losses, regulatory penalties, and reputational damage that takes years to repair.

At a global investment bank, I worked on post-trade systems processing hundreds of millions of pounds in daily transactions. These systems handled complex financial instruments like interest rate swaps, required strict regulatory compliance (EMIR, MiFID II), and demanded 99.99% uptime with sub-second response times. This post covers the architecture decisions that made that possible.

What you will learn: Event-driven microservices architecture for fintech · Trade compression engine design · Real-time risk calculation at scale · Database optimization and intelligent batching · Regulatory compliance automation (EMIR, MiFID II) · Performance results: 75% faster processing, 62% less memory, 95% fewer queries


1. The Domain: Post-Trade Processing

When two banks execute a trade, the work is not done. What follows - post-trade processing - is a complex chain of confirmations, calculations, settlements, and regulatory reports. At a global investment bank, this meant:

  • Trade compression: Identifying and netting offsetting interest rate swap positions to reduce capital requirements.
  • Settlement: Confirming trades with counterparties and managing the actual transfer of funds.
  • Risk calculation: Computing real-time exposure across all positions using distributed computing.
  • Regulatory reporting: Generating EMIR and MiFID II reports with automated audit trails.

Diagram 1: Post-trade processing lifecycle

TradeExecutedCompressionEngineRiskCalculationRegulatoryReporting✓Each stage must complete within seconds. Failure at any stage blocks the entire pipeline.

2. Architecture: Event-Driven Microservices

The core of the platform was an event-driven microservices architecture. Each stage of the pipeline was an independent service communicating through a message bus. This gave us three critical properties:

  • Isolation: A failure in the risk calculator did not affect the compression engine or the reporting pipeline.
  • Independent scaling: We could scale the compression engine during peak trading hours without touching other services.
  • Auditability: Every event was logged with a timestamp, producer, and consumer - providing a complete audit trail for regulators.

Diagram 2: Event-driven microservices architecture

Trade FeedCompression ServiceSettlement ServiceRisk ServiceReporting ServiceEMIR ✓Message Bus (Kafka)50+ system integrations

The Message Bus

Kafka was our backbone. Every service produced events to topics and consumed from topics. This decoupling meant we could add new services (like a new regulatory report type) without changing existing ones:

// Simplified Kafka event schema
{
  "event_id": "evt_20250315_001",
  "event_type": "trade.settled",
  "producer": "settlement-service",
  "timestamp": "2025-03-15T14:30:00Z",
  "payload": {
    "trade_id": "IRS-2025-03-15-0042",
    "counterparty": "BANK_A",
    "notional_amount": 50000000.00,
    "currency": "GBP",
    "settlement_status": "confirmed"
  }
}

3. Trade Compression Engine

Trade compression is the algorithmic heart of the system. Two banks that have multiple offsetting interest rate swap positions can compress them - netting out the offsetting positions to reduce the total notional exposure. Fewer positions means less capital required under regulatory rules.

Diagram 3: Trade compression - before and after

BeforeSwap A: +$50MSwap B: -$30MSwap C: -$20M→AfterNet: $0 (fully compressed)Capital requirement: $50M → $0

Compression Algorithm

from dataclasses import dataclass

@dataclass
class InterestRateSwap:
    id: str
    counterparty: str
    notional: float       # Positive = receive, negative = pay
    currency: str
    maturity_date: str
    fixed_rate: float
    floating_rate_index: str

def find_compression_opportunities(
    positions: list[InterestRateSwap],
) -> list[list[InterestRateSwap]]:
    """Group offsetting positions that can be compressed."""
    # Group by counterparty + currency + maturity bucket
    groups = {}
    for pos in positions:
        key = (pos.counterparty, pos.currency, pos.maturity_date[:7])
        groups.setdefault(key, []).append(pos)

    opportunities = []
    for key, group in groups.items():
        net_notional = sum(p.notional for p in group)
        if abs(net_notional) < sum(p.notional for p in group) * 0.1:
            # Offsetting positions found - net is < 10% of gross
            opportunities.append(group)

    return opportunities

# Example: 3 swaps with different directions
swaps = [
    InterestRateSwap("A", "BANK_A", 50_000_000, "GBP", "2030-06", 0.035, "SONIA"),
    InterestRateSwap("B", "BANK_A", -30_000_000, "GBP", "2030-06", 0.038, "SONIA"),
    InterestRateSwap("C", "BANK_A", -20_000_000, "GBP", "2030-06", 0.036, "SONIA"),
]

opportunities = find_compression_opportunities(swaps)
print(f"Found {len(opportunities)} compression opportunities")
# Output: Found 1 compression opportunity ($50M + -$30M + -$20M = $0 net)

4. Performance Optimization

The original compression engine ran a batch process every night that took 45 minutes. After optimization, we got it down to 11 minutes. Three changes drove that improvement:

1. Parallel Processing

The compression algorithm is embarassingly parallel - each counterparty group can be processed independently:

from concurrent.futures import ProcessPoolExecutor
import multiprocessing as mp

def parallel_compress(all_positions: list) -> list:
    """Process counterparty groups in parallel."""
    groups = group_by_counterparty(all_positions)

    with ProcessPoolExecutor(max_workers=mp.cpu_count()) as pool:
        results = list(pool.map(compress_group, groups))

    return merge_results(results)

2. Intelligent Batching of Database Queries

The original system made one database query per trade. We replaced N+1 queries with batched bulk operations:

# Before: N+1 queries (one per trade)
for trade in trades:
    counterparty = db.query("SELECT * FROM counterparties WHERE id = ?", trade.cp_id)
    print(f"Loaded {trade.id}: {counterparty.name}")

# After: single batch query
ids = [trade.cp_id for trade in trades]
placeholders = ",".join("?" for _ in ids)
counterparties = db.query(
    f"SELECT * FROM counterparties WHERE id IN ({placeholders})",
    *ids,
)
cp_map = {c.id: c for c in counterparties}

# 50,000 individual queries → 1 batch query: 95% reduction

Diagram 4: Performance optimization results

Processing TimeBefore: 45 minAfter: 11 min(-75%)Peak MemoryBefore: 2.4 GBAfter: 0.9 GB(-62%)DB Queries: -95% via intelligent batchingUptime: 99.99% target

3. Caching with Redis

Frequently accessed reference data (counterparty details, instrument definitions) was cached in Redis:

import redis
import json

cache = redis.Redis(host="localhost", port=6379, decode_responses=True)

def get_counterparty(cp_id: str) -> dict:
    """Get counterparty data with Redis caching."""
    cached = cache.get(f"cp:{cp_id}")
    if cached:
        return json.loads(cached)

    counterparty = db.query_one(
        "SELECT * FROM counterparties WHERE id = ?", cp_id
    )
    # Cache for 5 minutes (trades reference the same counterparties repeatedly)
    cache.setex(f"cp:{cp_id}", 300, json.dumps(counterparty))
    return counterparty

# Hit rate: ~85% during peak trading hours
# DB load reduced by 85% on read-heavy paths

5. Regulatory Compliance Automation

Financial regulation is not optional. EMIR (European Market Infrastructure Regulation) and MiFID II require detailed reporting of every trade, including:

  • Trade timestamps to the millisecond
  • Counterparty identification with LEI codes
  • Valuation details and collateral data
  • Modification and cancellation history

We automated compliance by building checks into the event pipeline. Every event carried a compliance metadata envelope:

@dataclass
class ComplianceEnvelope:
    event_id: str
    event_type: str
    timestamp_ns: int        # Nanosecond precision
    producer_service: str
    producer_version: str
    lei_codes: list[str]     # Legal Entity Identifiers
    audit_hash: str          # SHA-256 of the payload for tamper proofing

def validate_compliance(event: ComplianceEnvelope) -> bool:
    """Check that an event meets regulatory requirements."""
    checks = [
        bool(event.lei_codes),
        event.timestamp_ns > 0,
        len(event.audit_hash) == 64,
        event.producer_service in ALLOWED_SERVICES,
    ]
    return all(checks)

6. Lessons Learned

Building fintech systems at this scale taught me several lessons that apply well beyond trading:

Diagram 5: Key architectural principles for fintech

Event-Driven IsolationOne failure does not cascadeObservability-FirstEvery event logged, traced, auditableBatch Where Possible95% fewer queries via batchingCache Reference Data85% cache hit rate on readsDesign for audit before you design for speed

7. Summary: What to Remember

Event-Driven · Isolated · Observable · Auditable
  • Event-driven microservices provide the isolation and independent scaling that trading systems require. A failure in one service should never cascade.
  • Trade compression is an algorithmic problem first - the parallel processing and caching come after you have the right algorithm.
  • Intelligent batching (N+1 → bulk queries) produced the single biggest performance gain: 95% fewer database queries.
  • Compliance must be built in, not bolted on. Every event carries its regulatory metadata from the moment it is created.
  • Performance results: 75% faster processing, 62% less memory, 95% fewer queries - achieved through parallel processing, batching, and caching, not through magic.

Chapter

High Level Design

Reading level

Deep Dive

About the author

NS

Noah Sheldon

Applied AI/ML @ Fitch

Keep reading

Agentic Design

Beyond the Chatbot: The Rise of Agentic Orchestrators

The shift from conversational AI to autonomous execution. How agentic orchestrators work, why they matter, and what it takes to build them at enterprise scale.

Read · 8 min

High Level Design

System Design Fundamentals: Scaling Zero to Millions

Comprehensive guide to system design principles - from MVP architecture to distributed systems handling millions of requests.

Read · 15 min

High Level Design

Modern Tech Stack Evolution: From Monoliths to Microservices

How architectural decisions have shifted in the age of cloud-native development and serverless computing.

Read · 12 min
NS

Noah Sheldon

Associate Director, Applied AI/ML @ Fitch Ratings

Get a free AI opportunity audit

never miss a deep dive

New essays land when they ship. No spam, unsubscribe anytime.