0. How to use these notes

  • Treat this as a mini-chapter you can print and mark up.
  • Anything labelled REMEMBER or CORE SENTENCE is something you should be able to say in the interview.
  • If you can explain every section in this note in your own words, you are at least 80% ready on “pricing library design”.

1. Purpose of a pricing library in a bank

A “pricing library” here is not just code for Black–Scholes.

It is the shared engine used by:

  • trading (deal pricing, what-if),
  • structuring (new products, payoff design),
  • risk (daily Greeks, VaR, stress),
  • IT (batch jobs, APIs, integration).

Its job is to provide:

  1. Consistent prices and risks across all users.
  2. Deterministic, reproducible outputs that can be audited.
  3. Clear interfaces so that teams can build around it without breaking each other.
  4. Room to grow to new products, models, and asset classes.

REMEMBER: The real product is not a formula. The real product is a framework that can be trusted by trading, risk, and IT.


2. Core principles

Before architecture details, you should be able to state a few design principles clearly.

2.1 Separation of concerns

Each module should have one clear responsibility.

  • Instruments: economic contract and payoff.
  • Market: today’s state of the world.
  • Models: assumptions + parameters.
  • Pricers: numerical method.
  • Risk: sensitivities and scenarios.
  • Portfolio: aggregation and reporting.

CORE SENTENCE: “We separate what the contract pays, what the market looks like, which model we assume, and how we compute price and risk.”

2.2 Explicit inputs, no global state

  • No “global market” singleton.
  • No implicit “today” inside pricers.
  • No hidden random seeds.

Every major computation should take explicit objects as inputs:

  • Trade / Instrument
  • MarketSnapshot
  • Model (or model identifier)
  • PricerSettings and RiskSettings.

REMEMBER: “If I cannot see it in the function signature, it should not influence the result.”

2.3 Versioning and determinism

Two valuations are only comparable if they use the same:

  • market snapshot,
  • model version,
  • pricer version,
  • configuration, and
  • random seed (if applicable).

The library should enforce:

  • versioned market snapshots,
  • versioned models, and
  • run IDs that tie everything together.

CORE SENTENCE: “Same [trade, market_snapshot_id, model_id, pricer_version, seed] must always produce the same result.”

2.4 Growth and extensibility

The framework must:

  • accept new products without rewriting everything;
  • accept new models and pricers without breaking old flows;
  • be configuration-driven, not hard-coded.

REMEMBER: “We design a walking skeleton first, then plug in new products and models without touching core contracts.”


3. The six-module architecture

You should be able to draw and explain this diagram in the interview:

Instrument → Trade → Pricer ← Model ← Market → Risk → Portfolio

We group the world into six modules.

3.1 Instrument

Responsibility

  • Define the economic contract and payoff logic.
  • Examples: swap, FX forward, European option, autocallable note.

Knows:

  • notional, currency,
  • schedules, fixing rules,
  • strikes, barriers, call events,
  • payoff formulas in abstract form.

Does not know:

  • today’s spot, curves, or vol surfaces;
  • databases or market feeds.

Typical interfaces

  • get_cashflows(given_fixings)
  • get_payoff_spec() – a structured description of the payoff (for complex products).
  • For path-dependent: payoff(path) where path is a time series of underlyings.

CORE SENTENCE: “Instruments encode what the product pays under given fixings or paths, never how we value it today.”

Common mistakes

  • Instrument directly calling market data (e.g. self.get_spot()).
  • Instrument containing model assumptions (vol, correlation).
  • Instrument writing to logs, databases, etc.

3.2 Market

Responsibility

  • Provide a versioned snapshot of market data:
    • curves,
    • vol surfaces,
    • FX spots, basis,
    • historical fixings.

It is read-only and immutable within a run.

Typical structure

  • MarketSnapshot with:
    • snapshot_id
    • asof_date
    • build_time
    • source_tag (e.g. “EOD”, “INTRADAY”)
    • internal stores / maps for:
      • discount curves
      • forward curves
      • vol surfaces
      • FX rates
      • fixings.

Typical interfaces

  • get_curve(curve_id)
  • get_discount_factor(curve_id, date)
  • get_vol_surface(surface_id)
  • get_fixing(index_name, date)

REMEMBER: “Market is a read-only view of the world as of a specific timestamp. No fetching, no mutating during pricing.”

Common mistakes

  • Market object fetching data from external sources during pricing.
  • Market depending on instruments (e.g. curve that knows about a specific trade).
  • Mixing live streaming quotes with a supposedly frozen snapshot.

3.3 Model

Responsibility

  • Encode assumptions and calibrated parameters:
    • Black–Scholes, local vol, Heston, LMM, Hull–White, etc.
  • Connect a family of dynamics to a specific market snapshot.

A model object should know:

  • model type (e.g. “Heston FX”),
  • config (what was calibrated, what objective),
  • parameters (e.g. vol of vol, correlation),
  • calibration quality and diagnostics,
  • market snapshot it was calibrated to.

Typical structure

  • ModelConfig:
    • which instruments used for calibration,
    • what objective (least squares in implied vol, etc.),
    • constraints, bounds.
  • CalibratedModel:
    • model_id
    • model_type
    • params
    • market_snapshot_id
    • calibration_time
    • calibration_quality_metrics.

Interfaces

  • simulate_paths(...) for Monte Carlo.
  • characteristic_function(...) for Fourier methods.
  • Accessors for parameters and risk factors.

CORE SENTENCE: “Models are versioned artefacts: (model_type, config, market_snapshot, params) → model_id.”

Common mistakes

  • Recalibrating inside each pricing call.
  • Models silently reading latest market data instead of the passed snapshot.
  • Models tied to a single product type instead of an asset class.

3.4 Pricer

Responsibility

  • Implement the numerical method to value a trade:
    • analytic, tree, PDE, Monte Carlo, regression, approximation.
  • Take instrument + market + model and compute price and possibly primary Greeks.

A pricer should be:

  • stateless (no hidden mutable global state),
  • pure (deterministic for given inputs),
  • replaceable (you can plug in a better method later).

Typical interface

  • PricerResult price(Trade, MarketSnapshot, Model, PricerSettings)

where PricerResult includes:

  • price,
  • diagnostics (errors, iteration count),
  • maybe core Greeks (delta, vega) if they come “for free” with the method.

Performance and caching

  • Reuse:
    • calibrated model parameters,
    • precomputed grids / factorizations,
    • Monte Carlo paths if many payoffs share the same underlying simulation.

REMEMBER: “Pricers are pure functions: same [instrument, market, model, settings] → same result.”

Common mistakes

  • Pricer connecting directly to the DB to fetch market data.
  • Pricer deciding its own model choice dynamically without clear configuration.
  • Hidden mutable caches shared across threads without control.

3.5 Risk

Responsibility

  • Turn pricing into risk numbers:
    • Greeks,
    • scenarios,
    • stress,
    • hooks for VaR / expected shortfall.

It wraps the pricer with perturbations and aggregation, but should not change contract or market definitions.

Typical functions

  • compute_greeks(trade, market, model, pricer, risk_spec)
    • bump and reprice (parallel shifts, point shifts, volatility bumps).
    • or algorithmic differentiation (AAD) where available.
  • scenario_prices(trade, scenarios, model, pricer)
    • each scenario → modified market snapshot.

CORE SENTENCE: “Risk is a layer around pricing: it orchestrates bumps and scenarios, it does not redefine how payoffs work.”

Common mistakes

  • Risk engine directly encoding its own pricing logic.
  • Risk layer maintaining its own inconsistent view of market or model.
  • Entangling VaR batch logic with the core pricers (instead of using them via a clean API).

3.6 Portfolio

Responsibility

  • Aggregate trade-level outputs to:
    • desk,
    • book,
    • trader,
    • strategy,
    • legal entity.
  • Provide reporting views:
    • PV by bucket,
    • Greeks ladders,
    • P&L explain,
    • scenario P&L.

Portfolio does not reprice.

It consumes:

  • trade-level prices and Greeks,
  • scenario outputs,
  • metadata (hierarchy, ownership, tags).

REMEMBER: “Portfolio is about cutting and summing numbers, not recomputing them.”

Common mistakes

  • Portfolio layer re-implementing custom pricing shortcuts.
  • Portfolio relying on live, not versioned, numbers.
  • Aggregation logic hard-coded to one desk’s hierarchy.

4. Clean interfaces in practice

You should be able to move from slogans to concrete design.

4.1 Explicit input objects, no global state

Example clean pricing call

PricerResult result = pricer.price(
    trade = trade_object,
    market = market_snapshot_2026_01_31_EOD,
    model  = heston_model_v3,
    settings = pricer_settings_standard
)

Here:

  • There is no hidden “today”.
  • There is no global “current market”.
  • There is no hidden random seed.

REMEMBER: “All state is either in the input objects or derived deterministically from them.”

4.2 Stable contracts between modules

What this means

  • Once an interface like:
    • Instrument.get_payoff_spec()
    • Pricer.price(...)
    • RiskEngine.compute_greeks(...)
    is agreed, changes are:
    • backward-compatible, or
    • versioned explicitly (PricerV2).

Why it matters

  • Structuring can build GUIs around payoff specs.
  • Risk can build VaR hookups around the pricing API.
  • IT can build orchestration and scheduling around stable signatures.

CORE SENTENCE: “We keep contracts stable: inside a module we can refactor aggressively, but interfaces evolve carefully and explicitly.”


5. Versioning, determinism, and reproducibility

This is a core hiring point for a senior role. You must be very clear here.

5.1 Market snapshot versioning

Each snapshot has:

  • snapshot_id (string or numeric),
  • asof_date (business date),
  • build_time (timestamp),
  • source (EOD, intraday, vendor).

All curves and surfaces inside point to this snapshot.

REMEMBER: “A price always lives in the context of a specific market snapshot.”

5.2 Model versioning

Each calibrated model has:

  • model_id,
  • model_type (e.g. LMM, Heston),
  • market_snapshot_id,
  • config_id (calibration setup),
  • params,
  • calibration_time,
  • quality_metrics.

If you recalibrate with a new dataset or changed objective, you get a new model_id.

5.3 Run IDs and provenance

Every significant batch or pricing run should have a run ID that ties together:

  • list of trades / portfolios,
  • market snapshot id,
  • model ids (per asset class),
  • pricer version(s),
  • configuration (bump sizes, tolerances, number of paths),
  • code version (git hash),
  • timestamps and status.

CORE SENTENCE: “If a regulator asks ‘How did you price trade X on date Y?’, we should be able to reconstruct it from run metadata and re-run it.”

5.4 Monte Carlo and randomness

For MC:

  • Use explicit seeds:
    • seed_base in PricerSettings or RunConfig.
    • deterministically combine with trade ID and scenario ID if needed.
  • Keep path generation separate from payoff evaluation where possible.

REMEMBER: “Randomness must be controlled like any other input parameter.”


6. Product / pricer interface for structured products

A likely deep question: “How would you design a product/pricer interface for structured products?”

6.1 Payoff as a composition of primitives

Structured products combine:

  • digital coupons,
  • barriers,
  • autocalls,
  • min/max of several underlyings,
  • path-dependence.

Instead of creating a custom class for every product, you:

  1. Define payoff primitives:
    • call, put, digital, barrier, rebate, min, max, etc.
  2. Represent a product as a payoff specification:
    • a tree or expression (AST) built from primitives.
  3. Instruments expose this spec via:
    • get_payoff_spec().

6.2 Pricers operate on payoff specs

The pricer:

  • understands a class of payoff specs:
    • e.g. path-dependent equity payoffs under local vol.
  • translates the payoff spec into:
    • path-wise payoff evaluations (for MC),
    • or exercise rules (for trees / PDE).

You can have:

  • a high-accuracy MC pricer,
  • a faster approximation pricer for intraday risk,

both using the same product spec.

CORE SENTENCE: “Structured products are modelled as compositions of payoff primitives. Pricers operate on these payoff specifications plus the model and market, which decouples payoff design from numerical methods.”

6.3 Benefits

  • New structured products often require no new pricer, only a new specification.
  • New pricers (e.g. better MC, AAD-enabled versions) can be plugged in without changing product definitions.
  • Structuring GUIs can visualise and edit the payoff tree.

7. Avoiding circular coupling and dependency mess

The question: “How do you manage dependencies to avoid circular coupling?”

7.1 Strict layering

Enforce a one-directional dependency graph:

  • Core (dates, calendars, numerics, common types)
  • Instrument (depends on Core)
  • Market (depends on Core)
  • Model (depends on Core + Market)
  • Pricer (depends on Core + Instrument + Model + Market)
  • Risk (depends on Core + Pricer)
  • Portfolio (depends on Core + Risk/Results)

No module should import from a higher layer.

REMEMBER: “Dependencies flow from lower-level utilities up to higher-level orchestration, never the other way around.”

7.2 Dependency inversion and interfaces

High-level modules depend on interfaces, not concrete classes.

Examples:

  • Risk engine depends on PricerInterface, not on BlackScholesPricerImpl.
  • Portfolio aggregator depends on TradeResult data objects, not on concrete pricing methods.

Implementation binding is done via:

  • registries (maps from type → implementation),
  • or dependency injection (configs, factories).

7.3 Registries instead of hard references

Use:

  • ProductRegistry:
    • product_type → InstrumentClass.
  • PricerRegistry:
    • (product_type, model_type, purpose) → PricerClass.

This removes the need for modules to know each other directly.


8. How to grow the library

Question: “How do you structure the library so it can grow?”

8.1 Start with a “walking skeleton”

You implement a minimal but end-to-end setup:

  • Instruments:
    • deposits, FRAs, swaps, FX forwards, vanilla options.
  • Market:
    • basic discount curves,
    • simple vol surfaces.
  • Models:
    • lognormal for equity/FX,
    • simple one-factor IR model or even deterministic curves.
  • Pricers:
    • analytic solutions wherever possible.
  • Risk:
    • bump-and-revalue for a small set of risk factors.
  • Portfolio:
    • simple PV and delta aggregation per book.

CORE SENTENCE: “I would start with the most liquid curve and vanilla instruments that define the main risk factors. That gives us a skeleton we can use daily while we add complexity.”

8.2 Adding new products

To add a new product:

  1. Add a new instrument class or payoff spec.
  2. Register it in ProductRegistry.
  3. Implement or reuse a pricer:
    • map in PricerRegistry.
  4. Add tests and reference prices.

No changes should be required in:

  • other product classes,
  • core market code,
  • portfolio aggregators (beyond new tags or measures).

8.3 Adding new models

To add a new model:

  1. Implement the new model dynamics and calibration logic.
  2. Register model type in a model registry.
  3. Provide pricers that support (product_type, model_type) pairs.

We do not want:

  • product classes to know about specific models,
  • models to know about specific products.

9. Risk and portfolio integration – performance angle

For a senior role, you should show awareness of scale.

9.1 Greeks and sensitivity runs

Greeks via bump-and-revalue:

  • Bump key curves / surfaces in a structured way.
  • Use shared market snapshot with small deltas applied.
  • Reuse precomputations where possible:
    • discount factors,
    • interpolations,
    • MC paths for single-factor bumps.

If AAD is planned:

  • design pricers with differentiable operations in mind.
  • keep payoff evaluation and path generation structured.

9.2 Scenario and VaR usage

VaR / stress engines need:

  • a clean way to request valuations under many market scenarios;
  • ideally vectorised / batched calls:
    • scenario sets passed in bulk,
    • pricer computes many scenario prices in one go.

REMEMBER: “The framework must support many-scenario, many-trade workloads. That’s why clean, stateless pricers and shared market snapshots are crucial.”


10. Interview checklist – what you must be able to say

If you can confidently explain each bullet to a Head of Quant, you are in a strong position.

Architecture and responsibilities

  • Explain the six modules and what each is responsible for.

  • Explain the golden rule:

    “Instruments do not fetch data. Pricers do not own databases. Market data does not depend on instruments.”

  • Draw a simple diagram linking Instrument–Market–Model–Pricer–Risk–Portfolio and explain data flow.

Clean interfaces and determinism

  • Explain explicit input objects and “no global state”.
  • Explain stable contracts between modules and why backward compatibility matters.
  • Explain market snapshot versioning and model versioning.
  • Explain run IDs and what must be logged to reproduce valuations.

Structured products and extensibility

  • Explain payoff primitives and payoff specifications for structured products.
  • Explain how multiple pricers can serve the same product (MC vs proxy).
  • Explain how to add a new product or model without breaking the system.

Dependency management

  • Explain strict layering and why instruments and markets must not depend on pricers.
  • Explain dependency inversion and use of registries.
  • Explain why this avoids circular coupling and makes testing easier.

Performance and risk integration

  • Show awareness of bump-and-revalue vs AAD.
  • Explain how to run risk at scale (reusing market snapshots, batching scenarios).
  • Explain how portfolio aggregation is pure aggregation, not pricing.