Equities, ETFs, and the SEC

A hard Pipeline Design interview practice problem on DataDriven. Design the pipeline on the canvas: sources, transforms, storage and checks, then get feedback on it.

Domain
Pipeline Design
Difficulty
hard
Seniority
L5

Problem

Fractional share quantities and multi-currency settlement run through every trade a retail brokerage executes across equities, ETFs and crypto. Design the warehouse pipeline, data model and ingestion included, that serves business analytics, risk reporting and SEC/FINRA regulatory submissions.

Fractional shares, multi-currency, point-in-time. All of it.

Business requirements

Worked solution and explanation

This is three correctness promises dressed up as a brokerage warehouse: exact fractions, history that remembers, and records that cannot move. Anyone can draw a star schema with a trade fact and an account dimension. What separates candidates is seeing that each promise breaks quietly under the obvious design. An INT quantity truncates fractional shares. An overwritten dimension rewrites last year's report with today's account state. And an archive cut from the warehouse is only as unchanged as the last restatement, because it is a derived copy, not the trade record. You find out at the first audit.

Walk the requirements

Step 1: Land quantity in an exact decimal

A fifth of equity trades are fractional, so decimal_quantity_normalizer casts quantity to a fixed-scale NUMERIC before it reaches fact_trades. INT truncates the fraction and FLOAT drifts on aggregation, and either way a disputed balance will not reconcile.

Step 2: Keep account history as an SCD2

Margin status, account type and KYC status change. account_scd_loader writes one row per change into dim_account_scd2 with valid_from and valid_to. The load into fact_trades joins on trade_time BETWEEN valid_from AND valid_to, so a trade from last March reports under last March's account state.

Step 3: Archive the raw trades, not the warehouse

raw_trade_snapshot_writer runs daily and copies the trades exactly as they arrived on trade_execution_feed into six_year_write_once_archive, object storage under a six-year write-once retention policy. It reads the feed, not fact_trades, so no cast, join or restatement can touch what was filed. Regulators read only this store.

The feed forks: one branch normalizes into `fact_trades` with exact decimals and as-of account state, the other lands raw trades daily in `six_year_write_once_archive`, which regulators read.
  • trade_execution_feed (source, Kafka)
  • account_changes (source, PostgreSQL)
  • decimal_quantity_normalizer (transform, Spark)
  • account_scd_loader (transform, dbt)
  • fact_trades (storage, warehouse)
  • dim_account_scd2 (storage, warehouse)
  • raw_trade_snapshot_writer (transform, Spark); freshness: < 24h
  • six_year_write_once_archive (storage, object storage)
  • analytics_team (consumer, dashboard)
  • regulators (consumer, API)
  • trade_execution_feed -> decimal_quantity_normalizer
  • trade_execution_feed -> raw_trade_snapshot_writer
  • account_changes -> account_scd_loader
  • decimal_quantity_normalizer -> fact_trades
  • account_scd_loader -> dim_account_scd2
  • dim_account_scd2 -> fact_trades
  • raw_trade_snapshot_writer -> six_year_write_once_archive
  • fact_trades -> analytics_team
  • six_year_write_once_archive -> regulators
Current-state account dimension
One row per account_id, updated in place. Cheap equi-join, but the moment an account changes, every historical report is silently restated.
SCD2 account dimension
One row per change with valid_from and valid_to. The as-of join costs more and the table grows, but any past trade reports under the account state in effect then.

Common follow-up questions