2Brute2Best
πŸ”₯0
B3

Pre-Trade Risk Pipeline: KYC, Margin, Max Qty & Circuit Limits

High-frequency interview topic Pro OnlyChain of Responsibility std::expected (C++23)
⚑ 60-Second Architecture SummaryB3 · Chain of Responsibility
Naive Bottleneck→Chain of Responsibility Cure
The Naive Bottleneck: Violates Open-Closed Principle (OCP)

A monolithic 200-line `validateOrder()` function tangles KYC checks, tick size validation, margin calculations, and tier-specific exceptions into nested `if / else` blocks. A bug in an institutional quantity exemption accidentally bypassed the margin check, routing under-collateralized orders directly to the exchange.

Every new risk rule (e.g. leverage authorization or foreign investor limits) forces edits to the central validateOrder() function, risking regression in existing checks.

The Pattern Cure: Chain of Responsibility

Pass requests along a dynamic chain of handlers, where each handler evaluates its own rule and either short-circuits with an error or delegates to the next handler.

We define a RiskCheck base class holding a std::unique_ptr<RiskCheck> next_. Concrete handlers (KycCheck, MarginCheck, MaxQtyCheck, CircuitBandCheck) each focus on one invariant. If a rule fails, the handler immediately returns std::unexpected("reason"). If valid, it forwards to checkNext(). Different account pipelines (Retail, Institutional, Crypto) are assembled simply by linking different combinations of handlers.

Sprint 3 of 9 Β· Pre-Trade Risk Validation Pipeline
Story B β€” Low-Latency Trading System β€” Progressive System Build
1. Where our codebase left off
← B2

In Sprint 1 (B1 Β· Builder) we built validated TradeOrder values, and in Sprint 2 (B2 Β· Singleton) we centralized process-wide exchange limits via ExchangeConfig. Now, incoming orders must be evaluated against these limits before they can touch the live market.

2. What we are building now

We are building a pre-trade risk check gateway that validates compliance across KYC verification, account margin balance, max order quantity caps, and circuit price bands.

3. The pain point we hit

A monolithic 200-line validateOrder() function tangles KYC checks, tick size validation, margin calculations, and tier-specific exceptions into nested if / else blocks. A bug in an institutional quantity exemption accidentally bypassed the margin check, routing under-collateralized orders directly to the exchange.

4. What this unlocks next
Next: B4 β†’

With Chain of Responsibility (RiskCheck), each invariant lives in an independent link (KycCheck, MarginCheck, MaxQtyCheck, CircuitBandCheck) that can short-circuit immediately or delegate to next_, enabling dynamic pipelines per account tier. Next in Sprint 4 (B4 Β· Command), risk-approved orders must be executed against the OrderBook with full regulatory audit logging and batch rollback (undo) when a risk tripwire fires.

01

The product ticket

Ticket Β· B3TRADE-203 Β· Pre-Trade Risk Pipeline: KYC, Margin, Max Qty & Circuit Limits

Before an equity order reaches the market matching engine, it must pass a strict pre-trade risk check pipeline: 1) KYC verification (account active/compliant), 2) Margin balance (cash + collateral covers price * qty), 3) Max single-order quantity cap (retail limit e.g. 10,000 shares), and 4) Circuit price band (within Β±10% of last reference price).

Requirements
  • β€’

    Process validations sequentially: if any check fails, immediately short-circuit with a clear rejection reason and avoid running downstream expensive checks.

  • β€’

    Support differing pipeline configurations by account tier: Retail accounts require all 4 checks, Institutional accounts skip the retail quantity cap, and Crypto desks add a 5th leverage authorization check.

  • β€’

    Allow risk rules to be added, removed, or reordered without editing existing check classes or core trading engine code.

The next requirement that breaks naive code

The compliance team introduced a new margin check and an account-tier branching rule. In the naive codebase, developers added nested if / else statements to a 200-line validateOrder() function. A bug in the institutional quantity exemption accidentally bypassed the margin check, allowing an under-collateralized order to hit the exchange.

02

Step 1 β€” The naive code & why it breaks

A monolithic validateOrder() function uses a maze of nested if/else statements and boolean flags (isInstitutional, isCrypto, skipQtyCheck) to handle every validation rule in a single place.

Naive design (what not to ship) Β· naive.cpp
#include <cmath>
#include <print>
#include <string>

enum class AccountTier { Retail, Institutional, Crypto };

struct Order {
    std::string symbol;
    int qty;
    double price;
};

struct Account {
    std::string id;
    bool kycVerified{false};
    double marginBalance{0.0};
    AccountTier tier{AccountTier::Retail};
};

// Naive implementation: Monolithic validator with tangled if-else branches and tier flags.
// Adding or customizing a check requires modifying this function every time.
bool validateOrder(const Order& order, const Account& account, double refPrice,
                   bool hasLeverageAuth, std::string& error) {
    // 1. KYC verification
    if (!account.kycVerified) {
        error = "KYC verification pending";
        return false;
    }

    // 2. Margin balance check
    double cost = order.qty * order.price;
    if (account.marginBalance < cost) {
        error = "Insufficient margin balance";
        return false;
    }

    // 3. Max single-order quantity (Retail only!)
    if (account.tier == AccountTier::Retail) {
        if (order.qty > 10'000) {
            error = "Retail order exceeds max cap of 10,000 shares";
            return false;
        }
    }

    // 4. Circuit price band (within +-10% of reference price)
    double deviation = std::abs(order.price - refPrice) / refPrice;
    if (deviation > 0.10) {
        error = "Order price outside 10% circuit band";
        return false;
    }

    // 5. Crypto leverage check (Crypto accounts only!)
    if (account.tier == AccountTier::Crypto) {
        if (!hasLeverageAuth) {
            error = "Crypto account lacks leverage authorization";
            return false;
        }
    }

    error = "Approved";
    return true;
}

int main() {
    Account retail{"ACC-101", true, 50'000'000.0, AccountTier::Retail};
    Order largeOrder{"RELIANCE", 15'000, 2450.0};
    std::string err;

    // Fails on Retail due to hardcoded 10,000 limit
    if (!validateOrder(largeOrder, retail, 2450.0, false, err)) {
        std::println("Retail order rejected: {}", err);
    }
}
Violates Open-Closed Principle (OCP)

Every new risk rule (e.g. leverage authorization or foreign investor limits) forces edits to the central validateOrder() function, risking regression in existing checks.

Fragile account tier branching

Conditionals like if (tier == Retail) ... else if (tier == Institutional) are scattered throughout the function, making it difficult to verify which checks apply to which tier.

Inflexible execution order

Check order is hardcoded into the function. Reordering checks (e.g. running fast KYC before expensive margin calculations) requires rewriting and re-testing the entire validation flow.

03

Step 2 β€” The fix: Chain of Responsibility

Core idea in one sentence

Pass requests along a dynamic chain of handlers, where each handler evaluates its own rule and either short-circuits with an error or delegates to the next handler.

We define a RiskCheck base class holding a std::unique_ptr<RiskCheck> next_. Concrete handlers (KycCheck, MarginCheck, MaxQtyCheck, CircuitBandCheck) each focus on one invariant. If a rule fails, the handler immediately returns std::unexpected("reason"). If valid, it forwards to checkNext(). Different account pipelines (Retail, Institutional, Crypto) are assembled simply by linking different combinations of handlers.

Pattern roles in our story
Handler Interface

RiskCheck β€” defines setNext() and the pure virtual check(order, account) returning std::expected<void, std::string>.

Concrete Handlers

KycCheck, MarginCheck, MaxQtyCheck, CircuitBandCheck, CryptoLeverageCheck β€” validate specific invariants and forward or short-circuit.

Client / Assembler

The risk gateway that composes custom pipelines per account tier and feeds orders into the head of the chain.

Brute2Best Pro Content

Unlock Full UML Diagram & C++23 Solution

The interactive UML architecture diagrams, refactored C++23 code, real-world extensions, and interview Q&A are part of Brute2Best Pro. All 7 foundation chapters are 100% free.

All 440 visual dry runs & algorithms
Interactive step-by-step playback with pointer & array state transitions
52 comprehensive LLD chapters
Interactive UML diagrams, real-world stories (Food Delivery, Trading, RPG), and practice solutions
40 C++ basics labs & hardware memory models
Vtables, byte layouts, assembly insights, and multi-threading race conditions
Production-ready C++23 source code
Modern idioms, clean contracts, time/space complexity derivations, and interview takeaways

First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.

Systems Integration

Related C++ Systems & Algorithmic Foundations

Deep dive into underlying layers