ERM3 Risk Metrics API

Overview

The ERM3 Risk Metrics API provides programmatic access to equity risk model data computed by our ERM3 regression system. This site consumes and visualizes these published outputs in real-time dashboards and portfolio analytics.

Key Features:

  • Daily Updated: Fresh factor metrics, hedge ratios, and decompositions
  • OpenAPI Ready: Import into Postman, Swagger, or AI tools (ChatGPT, Claude)
  • Developer Friendly: TypeScript and Python examples with validation helpers
  • Secure: JWT + Row Level Security (RLS) for user auth, service role for backend agents

Quick Start

First successful call in minutes

TypeScript (Browser/Node.js)

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  'https://your-project.supabase.co',
  'your-service-role-key' // Server-side only
);

// Get latest metrics for AAPL
const { data, error } = await supabase
  .from('ticker_factor_metrics')
  .select('ticker, l3_residual_er, l3_market_hr, volatility')
  .eq('ticker', 'AAPL');

if (data) {
  const rr = data[0].l3_residual_er;
  console.log(`Residual Risk: ${(rr * 100).toFixed(1)}%`);
}

Python (Jupyter/Backend)

from supabase import create_client
import os

supabase = create_client(
    os.getenv('SUPABASE_URL'),
    os.getenv('SUPABASE_SERVICE_ROLE_KEY')
)

# Query latest metrics
response = supabase.table('ticker_factor_metrics') \
    .select('ticker, l3_residual_er, l3_market_hr, volatility') \
    .eq('ticker', 'AAPL') \
    .execute()

metrics = response.data[0]
print(f"Residual Risk: {metrics['l3_residual_er']:.1%}")
print(f"Market Hedge Ratio: {metrics['l3_market_hr']:.2f}")

cURL (REST API)

curl -X GET "https://your-project.supabase.co/rest/v1/ticker_factor_metrics?ticker=eq.AAPL" \
  -H "apikey: YOUR_SERVICE_ROLE_KEY" \
  -H "Content-Type: application/json"

Core API Endpoints

| Endpoint | Frequency | Use Case | |----------|-----------|----------| | /erm3_ticker_returns | Daily | Time series analysis of stock returns with factor decompositions | | /erm3_l3_decomposition | Monthly (1st trading day) | Historical risk attribution and hedge ratio evolution | | /ticker_factor_metrics | Daily (latest only) | Real-time dashboards, stock screening, portfolio construction |

Example Response

{
  "metadata": {
    "as_of": "2024-01-15",
    "last_updated": "2024-01-15T10:30:00Z",
    "frequency": "daily",
    "source_table": "ticker_factor_metrics",
    "units": {
      "hr": "dollar_ratio",
      "er": "decimal_fraction",
      "volatility": "annualized_decimal"
    }
  },
  "data": [
    {
      "ticker": "AAPL",
      "l3_residual_er": 0.54,
      "l3_market_hr": 0.98,
      "volatility": 0.25,
      "sharpe_ratio": 1.2
    }
  ]
}

Key Concepts

RR (Residual Risk)

Definition: Unexplained variance after hedging all factors (market, sector, subsector).

Formula: RR = 1 - (l3_market_er + l3_sector_er + l3_subsector_er)

Use Cases:

  • Screen for high RR (>0.5) to identify alpha opportunities
  • Risk budgeting: allocate capital to stocks with sufficient residual risk capacity
  • Portfolio construction: balance factor exposure vs. idiosyncratic risk

HR (Hedge Ratio)

Definition: The dollar amount of factor ETF to short per $1 of stock position to neutralize factor exposure.

Example: l3_market_hr = 0.98 means short $0.98 of SPY for every $1.00 long in the stock.

Use Cases:

  • Construct market-neutral portfolios
  • Calculate hedge quantities for factor-neutral strategies
  • Risk decomposition analysis

ER (Explained Risk)

Definition: Percentage of stock variance explained by factor regressions (R-squared).

Hierarchical Levels:

  • L1: Market-only (SPY beta)
  • L2: Market + Sector (GICS sector ETF)
  • L3: Market + Sector + Subsector (GICS subsector ETF)

Authentication Options

User-Facing Clients (Browser/Mobile)

Use JWT + Row Level Security (RLS) for user-facing applications:

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  'https://your-project.supabase.co',
  'your-anon-key' // Public key (safe to expose)
);

// User login
await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password'
});

// Token automatically included in queries
const { data } = await supabase
  .from('ticker_factor_metrics')
  .select('*')
  .eq('ticker', 'AAPL');

Backend Agents (Server-Side)

Use service_role key (full access, bypasses RLS):

# ✅ Server-side only - NEVER expose in browser
supabase = create_client(
    os.getenv('SUPABASE_URL'),
    os.getenv('SUPABASE_SERVICE_ROLE_KEY') # From environment variable
)

# Full access (no RLS)
response = supabase.table('ticker_factor_metrics').select('*').execute()

Error Handling

API errors follow a structured format:

{
  "error": "TICKER_NOT_FOUND",
  "message": "Ticker 'XYZABC' not found in universe 'uni_mc_3000'. Check ticker spelling or try a different universe.",
  "code": 404,
  "details": {
    "field": "ticker",
    "received": "XYZABC",
    "universe": "uni_mc_3000",
    "suggestion": "Check for typos or use ticker search endpoint"
  }
}

Common Errors:

  • 401 AUTHENTICATION_REQUIRED: Missing or invalid JWT/service_role key
  • 404 TICKER_NOT_FOUND: Ticker doesn't exist in specified universe
  • 404 DATE_NOT_AVAILABLE: Non-trading day or out of range
  • 429 RATE_LIMIT_EXCEEDED: Too many requests (implement client-side throttling)

Response Metadata

All responses include metadata for freshness validation and unit tracking:

  • as_of - Date of data snapshot
  • last_updated - Timestamp of last data refresh
  • frequency - "daily" or "monthly"
  • units - Field units (e.g., "decimal_fraction" for ER, "dollar_ratio" for HR)
  • record_count - Number of records returned
  • query_time_ms - Query execution time

Client-side validation:

from datetime import datetime, timedelta

def validate_freshness(metadata, max_age_days=7):
    as_of_date = datetime.fromisoformat(metadata['as_of'])
    age_days = (datetime.utcnow().date() - as_of_date.date()).days
    return age_days <= max_age_days

metadata, data = client.get_latest_metrics('AAPL')
if not validate_freshness(metadata, max_age_days=3):
    print("Warning: Data is stale")

Use Cases

1. Stock Screening for Alpha

# Find stocks with high residual risk (>50%)
response = supabase.table('ticker_factor_metrics') \
    .select('ticker, l3_residual_er, volatility, sharpe_ratio') \
    .gte('l3_residual_er', 0.5) \
    .order('l3_residual_er', desc=True) \
    .limit(50) \
    .execute()

high_alpha_stocks = response.data

2. Factor-Neutral Portfolio Construction

# Get hedge ratios for portfolio
tickers = ['AAPL', 'MSFT', 'GOOGL']
metrics = []

for ticker in tickers:
    response = supabase.table('ticker_factor_metrics') \
        .select('ticker, l3_market_hr, l3_sector_hr, l3_subsector_hr') \
        .eq('ticker', ticker) \
        .execute()
    metrics.append(response.data[0])

# Calculate hedge quantities
position_size = 100000  # $100k per stock
for m in metrics:
    spy_hedge = position_size * m['l3_market_hr']
    print(f"{m['ticker']}: Short ${spy_hedge:.0f} of SPY")

3. Time Series Risk Attribution

# Analyze residual risk trend over time
response = supabase.table('erm3_l3_decomposition') \
    .select('date, l3_residual_er') \
    .eq('ticker', 'AAPL') \
    .eq('market_factor_etf', 'SPY') \
    .eq('universe', 'uni_mc_3000') \
    .order('date', ascending=True) \
    .execute()

df = pd.DataFrame(response.data)
df['date'] = pd.to_datetime(df['date'])
df.plot(x='date', y='l3_residual_er', title='AAPL Residual Risk Over Time')

Full Documentation

For complete API reference, schemas, and integration guides, see the ERM3 API Documentation Repository:

| Document | Description | |----------|-------------| | OPENAPI_SPEC.yaml | Complete API contract with schemas and examples | | SEMANTIC_ALIASES.md | Field definitions, formulas, and source mappings | | AUTHENTICATION_GUIDE.md | JWT, service_role, token management, AI agent patterns | | RESPONSE_METADATA.md | Metadata contract, units, freshness validation | | ERROR_SCHEMA.md | Error codes, recovery patterns, debugging | | VALIDATION_HELPERS.md | Data quality checks, alignment validators |


How This Site Uses ERM3

Model Computation: All risk model regressions (Huber/Ridge) are computed in the ERM3 repository using Python, NumPy, and scikit-learn.

Data Consumption: This website (riskmodels.net) consumes published ERM3 outputs from Supabase and visualizes them in:

  • Portfolio analytics dashboards
  • Risk decomposition narratives
  • Interactive factor analysis charts
  • Hedge ratio calculators

Support


Last Updated: February 10, 2026
API Version: 1.0.0