Supporting NSE and BSE Without Mixing Components
Constructing components piecemeal via scattered `if (exchange == "NSE")` checks led a developer to pair an `NseOrderRouter` with a `BseFeeCalculator`. BSE's higher transaction charges were billed on NSE executions and BSE numeric scrip codes were sent to NSE's FIX gateway, resulting in exchange rejects and regulatory billing fines.
Nothing in the type system prevents an engineer or configuration script from pairing an NseOrderRouter with a BseFeeCalculator. Mismatches compile cleanly and cause silent runtime financial errors.
Provide an interface for creating families of related or dependent objects without specifying their concrete classes, ensuring that clients always receive compatible components.
We declare abstract product interfaces (OrderRouter, FeeCalculator, SymbolValidator) and an ExchangeFactory interface with creation methods for each product. Concrete factories (NseExchangeFactory, BseExchangeFactory) implement these methods to return their matching triplet of products. The TradingSession receives an ExchangeFactory reference and instantiates its components in one atomic step — it is physically impossible to mix components across different exchange families.
In Sprint 4 (B4 · Command), we wrapped order actions into auditable and reversible OrderCommand executions against our local order book. Now our brokerage is expanding beyond a single market to route orders to multiple exchange venues: NSE, BSE, and Crypto Spot.
We are building the multi-exchange session manager, supplying each trading session with its required triplet of exchange-specific components: OrderRouter (binary/FIX gateway), FeeCalculator (STT, stamp duty, transaction fees), and SymbolValidator (ticker format and scrip codes).
Constructing components piecemeal via scattered if (exchange == "NSE") checks led a developer to pair an NseOrderRouter with a BseFeeCalculator. BSE's higher transaction charges were billed on NSE executions and BSE numeric scrip codes were sent to NSE's FIX gateway, resulting in exchange rejects and regulatory billing fines.
By introducing Abstract Factory (ExchangeFactory), concrete factories (NseExchangeFactory, BseExchangeFactory) guarantee that routers, fee calculators, and symbol validators are created as an atomic, compatible family. Next in Sprint 6 (B6 · Mediator), inside our matching engine core, OrderBook, RiskEngine, PositionTracker, and multiple trading desks start calling each other directly in an N × N spaghetti web whenever a fill occurs.
The product ticket
Our trading terminal routes orders to multiple Indian exchanges: NSE (National Stock Exchange) and BSE (Bombay Stock Exchange). Each exchange requires a matching family of three specialized components: an OrderRouter (handles binary/FIX protocol and gateway URLs), a FeeCalculator (calculates exchange transaction charges, STT, and stamp duty), and a SymbolValidator (enforces exchange-specific ticker symbology).
- •
Ensure an exchange trading session always operates with a consistent, compatible family of components: an
NseOrderRoutermust NEVER be paired with aBseFeeCalculatororBseSymbolValidator. - •
Allow adding support for new exchanges (such as MCX for commodities or international venues) without modifying core trading algorithms or order execution services.
- •
Decouple client order workflows from the concrete implementations of individual exchange routers, fee schedules, and symbol parsers.
In the naive codebase, components were constructed piecemeal via scattered if (exchange == "NSE") checks across several files. A developer configuring a new desk terminal accidentally paired NseOrderRouter with BseFeeCalculator. Because BSE's higher transaction charges were billed on NSE executions and BSE numeric scrip codes were sent to the NSE gateway, orders were rejected by the exchange and traders were incorrectly billed.
Step 1 — The naive code & why it breaks
The naive approach constructs routers, calculators, and validators individually using string checks or enum switches scattered across multiple setup functions.
#include <cstdint>
#include <format>
#include <memory>
#include <print>
#include <string>
#include <string_view>
// Naive implementation: Components are instantiated piecemeal via scattered conditionals.
// There is zero type safety or compile-time guarantee preventing an engineer from
// pairing an NSE router with a BSE fee calculator or BSE symbol validator.
class NaiveNseRouter {
public:
std::string route(std::string_view symbol, int qty) {
return std::format("[NSE-NEAT] Routed {} of {}", qty, symbol);
}
};
class NaiveBseRouter {
public:
std::string route(std::string_view symbol, int qty) {
return std::format("[BSE-BOLT] Routed {} of {}", qty, symbol);
}
};
class NaiveNseFeeCalc {
public:
int64_t calculate(int64_t turnoverPaise) {
return (turnoverPaise * 325) / 10'000'000; // NSE rate
}
};
class NaiveBseFeeCalc {
public:
int64_t calculate(int64_t turnoverPaise) {
return (turnoverPaise * 375) / 10'000'000; // BSE rate
}
};
int main() {
std::string exchange = "NSE";
std::string feeTier = "BSE"; // Config typo or wrong parameter flag!
auto nseRouter = std::make_unique<NaiveNseRouter>();
auto bseFee = std::make_unique<NaiveBseFeeCalc>(); // ACCIDENTAL MISMATCH!
// The trading session is now running with an NSE router but charging BSE fee schedules!
int64_t turnoverPaise = 100'000'000; // Rs. 1,000,000
std::println("{}", nseRouter->route("RELIANCE", 100));
std::println("Calculated Fee (Wrong Exchange Rate!): {} paise", bseFee->calculate(turnoverPaise));
}Nothing in the type system prevents an engineer or configuration script from pairing an NseOrderRouter with a BseFeeCalculator. Mismatches compile cleanly and cause silent runtime financial errors.
Introducing a new exchange (e.g. MCX) requires hunting down and modifying every conditional block across router setup, fee billing, and symbol parsing files.
The knowledge of which component variants belong together is scattered across various calling functions rather than centralized in one cohesive factory.
Step 2 — The fix: Abstract Factory Pattern
Provide an interface for creating families of related or dependent objects without specifying their concrete classes, ensuring that clients always receive compatible components.
We declare abstract product interfaces (OrderRouter, FeeCalculator, SymbolValidator) and an ExchangeFactory interface with creation methods for each product. Concrete factories (NseExchangeFactory, BseExchangeFactory) implement these methods to return their matching triplet of products. The TradingSession receives an ExchangeFactory reference and instantiates its components in one atomic step — it is physically impossible to mix components across different exchange families.
ExchangeFactory — declares createRouter(), createFeeCalculator(), createSymbolValidator(), and exchangeName().
NseExchangeFactory, BseExchangeFactory — each instantiates the coherent family of products for that specific exchange.
OrderRouter, FeeCalculator, SymbolValidator — interfaces defining communication, fee calculation, and symbol validation.
NseOrderRouter, NseFeeCalculator, NseSymbolValidator vs BseOrderRouter, BseFeeCalculator, BseSymbolValidator.
TradingSession — initialized with an ExchangeFactory, interacting only with the abstract product interfaces.
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.
First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.