A securities data platform built on TimescaleDB (PostgreSQL 16 + time-series extension) with a FastAPI REST layer. The database contains 224 GB of realistic financial data spanning 1,000 securities across 5 years (2020–2025).
TimescaleDB (PostgreSQL 16) running in Docker. Hypertable for tick data with 1-month chunk intervals.
FastAPI (Python) with SQLAlchemy. Auto-generated Swagger docs. 9 REST endpoints.
Python scripts using NumPy/Pandas. SQL server-side generation for tick data via generate_series.
Docker Compose for database orchestration. Python venv for API dependencies.
| Table | Rows | Description |
|---|---|---|
tick_data | 1,600,268,670 | 20-second intraday ticks (TimescaleDB hypertable) |
pricing_history | 1,369,000 | Daily OHLCV for 1,000 securities, 5 years |
holdings_history | 1,040,440 | Daily portfolio snapshots across 7 portfolios |
transactions | 786,091 | Trade history (~50 trades/security/year) |
variance_quarterly | 29,400 | 21 quarterly attribution snapshots |
| Table | Rows | Description |
|---|---|---|
securities_master | 1,000 | 550 equity, 290 bonds, 90 derivatives, 45 structured, 25 other |
issuer | 209 | Issuers across 15 countries |
portfolio | 7 | 3 Equity, 2 Fixed Income, 1 Mixed, 1 Hedge |
holdings_period_1 | 156 | Snapshot at 2024-12-31 |
holdings_period_2 | 167 | Snapshot at 2025-03-31 |
pricing_period_1 | 120 | Period 1 prices (original 120 securities) |
pricing_period_2 | 120 | Period 2 prices |
variance_summary | 169 | Period-over-period attribution |
risk_metrics | 74 | VaR, beta, convexity |
credit_ratings | 61 | Rating history with upgrades/downgrades |
fx_rates | 26 | Currency rates to USD |
corporate_actions | 12 | Splits, dividends, coupons, maturities |
securities-platform/
├── api/
│ ├── main.py # FastAPI app entry point
│ ├── deps.py # Database session dependency
│ ├── models/
│ │ └── schemas.py # Pydantic response schemas
│ └── routers/
│ ├── securities.py # /api/securities endpoints
│ ├── portfolios.py # /api/portfolios, /api/holdings
│ ├── variance.py # /api/variance endpoints
│ └── reference.py # /api/corporate-actions, /api/fx-rates
├── database/
│ ├── schema.sql # Core table definitions (13 tables)
│ ├── schema_scale.sql # Scale tables (tick_data, pricing_history, etc.)
│ ├── indexes.sql # Performance indexes
│ └── loader.py # CSV-to-PostgreSQL loader
├── generator/
│ ├── generate_data.py # Original 120-security generator
│ └── generate_scale_data.py # Scale generator (1,000 securities, 224 GB)
├── docker/
│ └── docker-compose.yml # TimescaleDB service
├── data/
│ ├── schema/ # Source schema files
│ └── generated/ # Generated CSV files
├── .env # Environment configuration
├── .env.example # Template
└── requirements.txt # Python dependencies
cd /Users/imac/securities-platform
docker compose -f docker/docker-compose.yml up -d
Verify it's running:
docker exec securities-db psql -U securities_user -d securities_platform \
-c "SELECT pg_size_pretty(pg_database_size(current_database()));"
cd /Users/imac/securities-platform
source venv/bin/activate
uvicorn api.main:app --host 0.0.0.0 --port 8000
The API will be available at http://localhost:8000.
curl http://localhost:8000/api/health
# {"status":"ok","service":"securities-platform","version":"1.0.0"}
When the API is running, FastAPI provides auto-generated interactive documentation:
http://localhost:8000/docshttp://localhost:8000/redocBase URL: http://localhost:8000
curl http://localhost:8000/api/health
# Response:
{"status": "ok", "service": "securities-platform", "version": "1.0.0"}
| Parameter | Type | Default | Description |
|---|---|---|---|
security_type | string | — | EQUITY, BOND, OPTION, FUTURE, CLO, ABS |
sector | string | — | Issuer sector: Technology, Healthcare, Financials, etc. |
currency | string | — | USD, EUR, GBP, JPY, etc. |
limit | int | 100 | Max results (up to 500) |
offset | int | 0 | Pagination offset |
# All equities (first 10)
curl "http://localhost:8000/api/securities?security_type=EQUITY&limit=10"
# Technology sector in USD
curl "http://localhost:8000/api/securities?sector=Technology¤cy=USD"
# Page 2
curl "http://localhost:8000/api/securities?limit=100&offset=100"
curl http://localhost:8000/api/securities/SEC-0001
# Response:
{
"security_id": "SEC-0001",
"isin": "US0000000001",
"ticker": "TECH_A",
"security_name": "TechCorp Alpha",
"security_type": "EQUITY",
"asset_class": "Equity",
"currency": "USD",
"exchange": "NYSE",
"issuer_id": "ISS-001",
"status": "ACTIVE"
}
curl http://localhost:8000/api/portfolios
| Parameter | Type | Default | Description |
|---|---|---|---|
portfolio_id | string | — | e.g. PORT-001 |
period | int | 1 | 1 = 2024-12-31, 2 = 2025-03-31 |
limit | int | 100 | Max results (up to 500) |
# PORT-001 holdings at period 1
curl "http://localhost:8000/api/holdings?portfolio_id=PORT-001&period=1"
# Period 2 top holdings by market value
curl "http://localhost:8000/api/holdings?period=2&limit=20"
curl http://localhost:8000/api/variance/portfolio/PORT-001
# Response:
{
"summary": {
"portfolio_id": "PORT-001",
"total_mv_start": 125000000.00,
"total_mv_end": 131500000.00,
"total_price_effect": 4200000.00,
"total_qty_effect": 1100000.00,
"total_fx_effect": -300000.00,
"total_corp_action_effect": 1500000.00,
"total_pnl": 6500000.00
},
"details": [ ... ]
}
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | 20 | Max results (up to 100) |
curl "http://localhost:8000/api/variance/top-movers?limit=5"
| Parameter | Type | Default | Description |
|---|---|---|---|
security_id | string | — | Filter by security |
action_type | string | — | STOCK_SPLIT, DIVIDEND, COUPON, MATURITY |
curl http://localhost:8000/api/corporate-actions
curl "http://localhost:8000/api/corporate-actions?action_type=DIVIDEND"
| Parameter | Type | Default | Description |
|---|---|---|---|
from_currency | string | — | Filter by source currency (e.g. EUR) |
rate_date | string | — | Filter by date (e.g. 2024-12-31) |
curl http://localhost:8000/api/fx-rates
curl "http://localhost:8000/api/fx-rates?from_currency=GBP"
# Through Docker
docker exec -it securities-db psql -U securities_user -d securities_platform
# From host
psql -h localhost -p 5432 -U securities_user -d securities_platform
# Password: securities_pass
postgresql://securities_user:securities_pass@localhost:5432/securities_platform
-- Database size
SELECT pg_size_pretty(pg_database_size(current_database()));
-- Row counts for all tables
SELECT schemaname, relname, n_live_tup
FROM pg_stat_user_tables
WHERE schemaname = 'public'
ORDER BY n_live_tup DESC;
-- Table sizes
SELECT tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename))
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
-- Securities by type
SELECT security_type, count(*) FROM securities_master GROUP BY security_type;
-- Daily price for a security
SELECT price_date, close_price, volume
FROM pricing_history
WHERE security_id = 'SEC-0001'
ORDER BY price_date DESC
LIMIT 10;
-- Intraday ticks for a security on a specific day
SELECT tick_time, price, bid, ask, volume
FROM tick_data
WHERE security_id = 'SEC-0001'
AND tick_time >= '2025-03-28 09:30:00'
AND tick_time < '2025-03-28 16:00:00'
ORDER BY tick_time;
-- Portfolio holdings on a date
SELECT h.security_id, sm.security_name, h.quantity, h.market_value, h.weight
FROM holdings_history h
JOIN securities_master sm ON h.security_id = sm.security_id
WHERE h.portfolio_id = 'PORT-001'
AND h.as_of_date = '2025-03-31'
ORDER BY h.market_value DESC
LIMIT 20;
-- Quarterly variance for a portfolio
SELECT quarter_label, SUM(total_pnl) as total_pnl,
SUM(price_effect) as price_eff, SUM(fx_effect) as fx_eff
FROM variance_quarterly
WHERE portfolio_id = 'PORT-001'
GROUP BY quarter_label
ORDER BY quarter_label;
-- Top 10 trades by value
SELECT t.security_id, sm.security_name, t.txn_type,
t.quantity, t.price, t.quantity * t.price as trade_value
FROM transactions t
JOIN securities_master sm ON t.security_id = sm.security_id
ORDER BY t.quantity * t.price DESC
LIMIT 10;
| Table | Columns |
|---|---|
issuer | issuer_id, issuer_name, country, sector, industry, lei |
securities_master | security_id, isin, cusip, sedol, ticker, security_name, security_type, asset_class, currency, exchange, issuer_id (FK), issue_date, maturity_date, coupon_rate, status |
portfolio | portfolio_id, portfolio_name, portfolio_type, benchmark, inception_date, currency |
holdings_period_1/2 | holding_id, security_id, portfolio_id, quantity, market_value, weight, cost_basis, accrued_interest, as_of_date |
pricing_period_1/2 | pricing_id, security_id, price_date, open_price, high_price, low_price, close_price, volume, vwap |
transactions | txn_id, security_id, portfolio_id, txn_type, quantity, price, txn_date, settlement_date, fees, currency |
fx_rates | rate_id, from_currency, to_currency, rate_date, rate |
credit_ratings | rating_id, issuer_id, rating, rating_date, agency, action |
corporate_actions | action_id, security_id, action_type, ex_date, record_date, payment_date, ratio, amount |
risk_metrics | metric_id, security_id, portfolio_id, var_95, var_99, beta, convexity, as_of_date, ... |
variance_summary | summary_id, portfolio_id, security_id, period_from, period_to, mv_start, mv_end, price_effect, qty_effect, fx_effect, corp_action_effect, total_pnl |
| Table | Columns | Notes |
|---|---|---|
pricing_history | security_id, price_date, open_price, high_price, low_price, close_price, volume, vwap | Standard table |
tick_data | tick_time, security_id, price, bid, ask, bid_size, ask_size, volume, trade_count, vwap | TimescaleDB hypertable, 1-month chunks |
holdings_history | security_id, portfolio_id, as_of_date, quantity, market_value, weight, cost_basis, accrued_interest | Standard table |
variance_quarterly | portfolio_id, security_id, quarter_label, period_from, period_to, mv_start, mv_end, price_effect, qty_effect, fx_effect, corp_action_effect, total_pnl | Standard table |
-- Core indexes (indexes.sql)
idx_sm_type, idx_sm_currency, idx_sm_issuer, idx_sm_isin, idx_sm_cusip, idx_sm_sedol
idx_hp1_security, idx_hp1_portfolio, idx_hp2_security, idx_hp2_portfolio
idx_pp1_security, idx_pp2_security
idx_txn_security, idx_txn_portfolio, idx_txn_date
idx_cr_issuer, idx_ca_security, idx_rm_security, idx_rm_portfolio
idx_vs_portfolio, idx_vs_security
-- Scale indexes
idx_ph_sec_date ON pricing_history(security_id, price_date)
idx_ph_date ON pricing_history(price_date)
idx_ph_sid ON pricing_history(security_id)
idx_td_sec ON tick_data(security_id, tick_time DESC)
idx_hh_port_date ON holdings_history(portfolio_id, as_of_date)
idx_hh_sec ON holdings_history(security_id)
idx_vq_port ON variance_quarterly(portfolio_id, quarter_label)
| Variable | Default | Description |
|---|---|---|
POSTGRES_USER | securities_user | Database username |
POSTGRES_PASSWORD | securities_pass | Database password |
POSTGRES_DB | securities_platform | Database name |
POSTGRES_HOST | localhost | Database host |
POSTGRES_PORT | 5432 | Database port |
API_HOST | 0.0.0.0 | API bind address |
API_PORT | 8000 | API port |
fastapi==0.115.6
uvicorn[standard]==0.34.0
sqlalchemy==2.0.36
psycopg2-binary==2.9.10
pandas==2.2.3
numpy==2.2.1
openpyxl==3.1.5
alembic==1.14.0
python-dotenv==1.0.1
To regenerate from scratch (warning: takes ~6 hours for full dataset):
cd /Users/imac/securities-platform
source venv/bin/activate
# Step 1: Generate original 120-security base data
python generator/generate_data.py
# Step 2: Load base data into database
python database/loader.py
# Step 3: Generate scale data (1,000 securities, 224 GB)
python generator/generate_scale_data.py
The scale generator is resumable — if interrupted, it will skip completed phases and continue tick data from the last completed security.