Neutryx Core Tutorials¶
Hands-on tutorials for mastering Neutryx Core. These tutorials progress from beginner to advanced topics, covering pricing, risk management, calibration, and production deployment.
Getting Started Tutorials¶
Tutorial 1: Your First Option Price¶
Level: Beginner | Time: 15 minutes
Learn the basics of option pricing with Black-Scholes.
import jax.numpy as jnp
from neutryx.models.bs import price, greeks
# Market setup
S = 100.0 # Spot price
K = 100.0 # Strike
T = 1.0 # Maturity (years)
r = 0.05 # Risk-free rate
q = 0.02 # Dividend yield
σ = 0.20 # Volatility
# Price call and put
call = price(S, K, T, r, q, σ, "call")
put = price(S, K, T, r, q, σ, "put")
print(f"Call: ${call:.4f}, Put: ${put:.4f}")
# Calculate Greeks
delta, gamma, vega, theta, rho = greeks(S, K, T, r, q, σ, "call")
print(f"Delta: {delta:.4f}, Gamma: {gamma:.4f}, Vega: {vega:.4f}")
Key Concepts: - Analytic pricing vs. numerical methods - Understanding Greeks - Put-call parity
Exercises: 1. Verify put-call parity holds 2. Calculate at-the-money (ATM) option value 3. Compare in-the-money (ITM) vs out-of-the-money (OTM) Greeks
Tutorial 2: Monte Carlo Simulation¶
Level: Beginner | Time: 20 minutes
Master Monte Carlo simulation for option pricing.
import jax
import jax.numpy as jnp
from neutryx.core.engine import MCConfig, simulate_gbm, present_value
# Setup
key = jax.random.PRNGKey(42)
S0, K, T = 100.0, 100.0, 1.0
r, q, σ = 0.05, 0.02, 0.20
# Configure simulation
config = MCConfig(
steps=252, # Daily steps
paths=100_000, # Number of simulations
seed=42
)
# Simulate GBM paths
paths = simulate_gbm(key, S0, r - q, σ, T, config)
# European call payoff
ST = paths[:, -1]
payoff = jnp.maximum(ST - K, 0.0)
call_price = present_value(payoff, jnp.array(T), r)
print(f"MC Price: ${float(call_price):.4f}")
# Confidence interval
std_error = jnp.std(payoff * jnp.exp(-r * T)) / jnp.sqrt(config.paths)
ci_95 = 1.96 * std_error
print(f"95% CI: [{call_price - ci_95:.4f}, {call_price + ci_95:.4f}]")
Key Concepts: - Geometric Brownian Motion (GBM) - Path simulation - Confidence intervals - Variance estimation
Exercises: 1. Increase paths and observe convergence 2. Calculate convergence rate (should be √N) 3. Implement antithetic variates for variance reduction
Tutorial 3: Path-Dependent Options¶
Level: Intermediate | Time: 30 minutes
Price exotic options that depend on the entire price path.
Asian Option (Arithmetic Average)¶
import jax
import jax.numpy as jnp
from neutryx.core.engine import MCConfig, simulate_gbm, present_value
key = jax.random.PRNGKey(42)
S0, K, T = 100.0, 100.0, 1.0
r, q, σ = 0.05, 0.02, 0.20
config = MCConfig(steps=252, paths=100_000)
paths = simulate_gbm(key, S0, r - q, σ, T, config)
# Arithmetic average
avg_price = jnp.mean(paths, axis=1)
payoff = jnp.maximum(avg_price - K, 0.0)
asian_price = present_value(payoff, jnp.array(T), r)
print(f"Asian Call: ${float(asian_price):.4f}")
Barrier Option (Up-and-Out)¶
# Up-and-out barrier call
barrier = 120.0
knocked_out = jnp.any(paths >= barrier, axis=1)
ST = paths[:, -1]
payoff = jnp.where(knocked_out, 0.0, jnp.maximum(ST - K, 0.0))
barrier_price = present_value(payoff, jnp.array(T), r)
print(f"Barrier Call: ${float(barrier_price):.4f}")
Lookback Option¶
# Lookback call (floating strike)
min_price = jnp.min(paths, axis=1)
ST = paths[:, -1]
payoff = ST - min_price
lookback_price = present_value(payoff, jnp.array(T), r)
print(f"Lookback Call: ${float(lookback_price):.4f}")
Exercises: 1. Implement down-and-in barrier option 2. Calculate geometric average Asian option 3. Compare lookback option with different monitoring frequencies
Risk Management Tutorials¶
Tutorial 4: Value at Risk (VaR)¶
Level: Intermediate | Time: 30 minutes
Calculate portfolio VaR using multiple methodologies.
Historical Simulation VaR¶
import jax.numpy as jnp
from neutryx.risk.var import historical_var, expected_shortfall
# Historical returns (daily)
returns = jnp.array([...]) # Load your returns data
# Calculate VaR at 95% and 99% confidence
var_95 = historical_var(returns, confidence=0.95)
var_99 = historical_var(returns, confidence=0.99)
print(f"VaR(95%): ${var_95:,.2f}")
print(f"VaR(99%): ${var_99:,.2f}")
# Expected Shortfall (CVaR)
es_95 = expected_shortfall(returns, confidence=0.95)
es_99 = expected_shortfall(returns, confidence=0.99)
print(f"ES(95%): ${es_95:,.2f}")
print(f"ES(99%): ${es_99:,.2f}")
Monte Carlo VaR¶
from neutryx.risk.var import monte_carlo_var
# Portfolio positions
positions = jnp.array([1_000_000, 2_000_000, 1_500_000])
# Simulate returns
key = jax.random.PRNGKey(42)
mean_returns = jnp.array([0.0001, 0.0002, 0.00015])
cov_matrix = jnp.array([
[0.0004, 0.0001, 0.0002],
[0.0001, 0.0009, 0.0003],
[0.0002, 0.0003, 0.0006]
])
var_mc = monte_carlo_var(
positions, mean_returns, cov_matrix,
confidence=0.95, num_simulations=10_000, key=key
)
print(f"Monte Carlo VaR(95%): ${var_mc:,.2f}")
Parametric VaR¶
from neutryx.risk.var import parametric_var
# Calculate using variance-covariance method
portfolio_value = jnp.sum(positions)
weights = positions / portfolio_value
# Portfolio statistics
portfolio_return = jnp.dot(weights, mean_returns)
portfolio_vol = jnp.sqrt(jnp.dot(weights, jnp.dot(cov_matrix, weights)))
var_param = parametric_var(
portfolio_value, portfolio_return, portfolio_vol,
confidence=0.95
)
print(f"Parametric VaR(95%): ${var_param:,.2f}")
Key Concepts: - Historical vs. parametric vs. Monte Carlo VaR - Expected Shortfall (CVaR) - Portfolio correlation and diversification - VaR backtesting
Exercises: 1. Compare all three VaR methods 2. Calculate component VaR for each position 3. Implement VaR backtesting framework
Tutorial 5: Position Limits and Pre-Trade Controls¶
Level: Intermediate | Time: 25 minutes
Implement risk limits and pre-trade checking.
from neutryx.risk.limits import (
PositionLimits, LimitChecker, LimitBreachLevel
)
# Define hierarchical limits
limits = PositionLimits(
notional_limit=10_000_000,
var_limit_hard=500_000,
var_limit_soft=400_000,
var_limit_warning=300_000,
concentration_limit=0.25,
issuer_exposure_limit=2_000_000
)
# Create limit checker
checker = LimitChecker(limits)
# Current portfolio state
current_state = {
"total_notional": 8_000_000,
"portfolio_var": 350_000,
"positions": {
"AAPL": {"notional": 2_000_000, "var": 100_000},
"MSFT": {"notional": 1_500_000, "var": 80_000},
"GOOGL": {"notional": 1_000_000, "var": 70_000}
}
}
# Proposed trade
proposed_trade = {
"ticker": "AAPL",
"notional": 500_000,
"var": 25_000,
"type": "buy"
}
# Pre-trade check
result = checker.check_trade(proposed_trade, current_state)
if result.approved:
print("✓ Trade approved")
else:
print(f"✗ Trade rejected: {result.reason}")
print(f" Breach level: {result.breach_level}")
print(f" Limit utilization: {result.utilization:.1%}")
# What-if analysis
what_if_var = checker.calculate_what_if_var(
proposed_trade, current_state
)
print(f"Post-trade VaR: ${what_if_var:,.2f}")
Key Concepts: - Hierarchical limit structures - Pre-trade impact analysis - Limit breach severity levels - Approval workflows
Exercises: 1. Implement desk-level limits 2. Add concentration limits by sector 3. Create limit breach alerting system
Model Calibration Tutorials¶
Tutorial 6: Heston Model Calibration¶
Level: Advanced | Time: 45 minutes
Calibrate the Heston stochastic volatility model to market data.
import jax
import jax.numpy as jnp
from neutryx.calibration.heston import calibrate_heston
from neutryx.calibration.diagnostics import calibration_diagnostics
from neutryx.calibration.model_selection import select_model_aic
# Market data: implied volatility surface
strikes = jnp.array([90, 95, 100, 105, 110])
maturities = jnp.array([0.25, 0.5, 1.0, 2.0])
spot = 100.0
# Observed market implied vols (example data)
market_vols = jnp.array([
[0.22, 0.20, 0.19, 0.21, 0.24], # T=0.25
[0.21, 0.19, 0.18, 0.20, 0.23], # T=0.5
[0.20, 0.18, 0.17, 0.19, 0.22], # T=1.0
[0.19, 0.17, 0.16, 0.18, 0.21], # T=2.0
])
# Convert to prices for calibration
from neutryx.models.bs import price as bs_price
market_prices = jnp.array([
[bs_price(spot, K, T, 0.05, 0.02, vol, "call")
for K, vol in zip(strikes, vols)]
for T, vols in zip(maturities, market_vols)
])
# Initial parameter guess
initial_params = {
"kappa": 2.0, # Mean reversion speed
"theta": 0.04, # Long-term variance
"sigma": 0.3, # Vol of vol
"rho": -0.7, # Correlation
"v0": 0.04 # Initial variance
}
# Calibrate
calibrated_params, diagnostics = calibrate_heston(
spot=spot,
strikes=strikes,
maturities=maturities,
market_prices=market_prices,
initial_params=initial_params,
r=0.05,
q=0.02
)
print("Calibrated Parameters:")
for param, value in calibrated_params.items():
print(f" {param}: {value:.6f}")
print(f"\nCalibration RMSE: {diagnostics['rmse']:.6f}")
print(f"Max absolute error: {diagnostics['max_error']:.6f}")
print(f"Iterations: {diagnostics['iterations']}")
# Model selection using AIC
aic = select_model_aic(
calibrated_params, market_prices, num_params=5
)
print(f"AIC Score: {aic:.2f}")
Key Concepts: - Implied volatility surface - Characteristic function pricing - Calibration objectives (RMSE, weighted least squares) - Parameter constraints and stability - Model selection criteria
Exercises: 1. Add Tikhonov regularization 2. Implement joint calibration with volatility smile 3. Compare Heston vs. SABR calibration quality
Tutorial 7: Model Selection and Validation¶
Level: Advanced | Time: 40 minutes
Select the best model using statistical criteria and cross-validation.
from neutryx.calibration.model_selection import (
select_model_aic, select_model_bic, select_model_aicc,
cross_validate_model, sensitivity_analysis
)
# Calibrate multiple models
models = {
"Black-Scholes": {"params": {...}, "num_params": 1},
"Heston": {"params": {...}, "num_params": 5},
"SABR": {"params": {...}, "num_params": 4}
}
# Information criteria comparison
for model_name, model_data in models.items():
aic = select_model_aic(model_data["params"], market_prices,
model_data["num_params"])
bic = select_model_bic(model_data["params"], market_prices,
model_data["num_params"])
aicc = select_model_aicc(model_data["params"], market_prices,
model_data["num_params"])
print(f"{model_name}:")
print(f" AIC: {aic:.2f}, BIC: {bic:.2f}, AICc: {aicc:.2f}")
# Cross-validation
cv_results = cross_validate_model(
model="heston",
data=market_prices,
k_folds=5,
metric="rmse"
)
print(f"\nCross-Validation RMSE: {cv_results['mean']:.6f} ± {cv_results['std']:.6f}")
# Sensitivity analysis
sensitivity = sensitivity_analysis(
calibrated_params,
market_prices,
parameter="kappa",
range_pct=0.20
)
print(f"\nParameter Sensitivity:")
for param, sens in sensitivity.items():
print(f" {param}: {sens:.4f}")
Key Concepts: - Information criteria (AIC, BIC, AICc, HQIC) - Cross-validation strategies - Parameter sensitivity and identifiability - Model comparison and selection
Exercises: 1. Implement time-series cross-validation 2. Calculate global sensitivity using Sobol indices 3. Test model stability under different market regimes
Advanced Topics¶
Tutorial 8: XVA Calculations¶
Level: Advanced | Time: 60 minutes
Calculate Credit Valuation Adjustment (CVA) and other XVA metrics.
from neutryx.valuations.cva import calculate_cva, calculate_dva
from neutryx.valuations.exposure import exposure_profile
# Simulate exposure over time
key = jax.random.PRNGKey(42)
num_paths = 10_000
time_grid = jnp.linspace(0, 5, 21) # Quarterly for 5 years
# Simulate underlying (swap rates, equity prices, etc.)
underlying_paths = simulate_gbm(key, S0=100, mu=0.05, sigma=0.20,
T=5.0, steps=20, paths=num_paths)
# Calculate exposure for interest rate swap (example)
notional = 10_000_000
fixed_rate = 0.05
discount_curve = jnp.array([...]) # Term structure
# Expected Exposure (EE)
ee = exposure_profile(underlying_paths, notional, time_grid)
# Potential Future Exposure (PFE) at 95%
pfe_95 = jnp.percentile(ee, 95, axis=0)
# Credit spreads and survival probabilities
counterparty_spreads = jnp.array([0.01, 0.012, 0.014, ...])
survival_probs = jnp.exp(-jnp.cumsum(counterparty_spreads * time_grid))
# Calculate CVA
recovery_rate = 0.40
cva = calculate_cva(
exposure=ee,
survival_probs=survival_probs,
recovery_rate=recovery_rate,
discount_factors=discount_curve
)
print(f"CVA: ${cva:,.2f}")
print(f"CVA as % of notional: {(cva/notional)*100:.4f}%")
# Calculate DVA (own credit risk)
own_spreads = jnp.array([0.005, 0.006, 0.007, ...])
dva = calculate_dva(
exposure=-ee, # Negative exposure from counterparty perspective
survival_probs=jnp.exp(-jnp.cumsum(own_spreads * time_grid)),
recovery_rate=recovery_rate,
discount_factors=discount_curve
)
print(f"DVA: ${dva:,.2f}")
print(f"Net CVA: ${cva - dva:,.2f}")
Key Concepts: - Expected Exposure (EE) and Potential Future Exposure (PFE) - CVA, DVA, FVA calculations - Wrong-way risk - Collateral modeling
Exercises: 1. Implement FVA (Funding Valuation Adjustment) 2. Model collateral with CSA terms 3. Calculate MVA (Margin Valuation Adjustment)
Tutorial 9: Real-Time Market Data Pipeline¶
Level: Advanced | Time: 50 minutes
Build a production-grade market data pipeline.
import asyncio
from neutryx.market.adapters import BloombergAdapter, BloombergConfig
from neutryx.market.storage import TimescaleDBStorage, TimescaleDBConfig
from neutryx.market.validation import (
ValidationPipeline, PriceRangeValidator,
SpreadValidator, VolumeValidator
)
from neutryx.market.feeds import FeedManager
# Configure Bloomberg adapter
bloomberg_config = BloombergConfig(
adapter_name="bloomberg",
host="localhost",
port=8194,
auth_token="your_token"
)
# Configure TimescaleDB with compression
storage_config = TimescaleDBConfig(
host="localhost",
port=5432,
database="market_data",
user="trader",
password="secure_password",
compression_enabled=True,
compression_age_days=7,
retention_policy_days=90
)
# Setup validation pipeline
validation = ValidationPipeline()
validation.add_validator(
PriceRangeValidator(max_jump_pct=0.20)
)
validation.add_validator(
SpreadValidator(max_bid_ask_spread_pct=0.05)
)
validation.add_validator(
VolumeValidator(min_volume=1000, spike_threshold=10.0)
)
# Create feed manager
async def run_market_data_pipeline():
adapter = BloombergAdapter(bloomberg_config)
storage = TimescaleDBStorage(storage_config)
manager = FeedManager(
adapters=[adapter],
storage=storage,
validation_pipeline=validation,
buffer_size=1000,
flush_interval=1.0
)
# Start feed
await manager.start()
# Subscribe to instruments
await manager.subscribe("equity", ["AAPL", "MSFT", "GOOGL", "AMZN"])
await manager.subscribe("fx", ["EURUSD", "GBPUSD", "USDJPY"])
# Monitor feed
while True:
stats = await manager.get_statistics()
print(f"Messages received: {stats['total_messages']}")
print(f"Quality score: {stats['average_quality']:.2f}")
print(f"Failed validations: {stats['failed_validations']}")
await asyncio.sleep(10)
# Run pipeline
asyncio.run(run_market_data_pipeline())
Key Concepts: - Real-time data ingestion - Data validation and quality scoring - TimescaleDB hypertables and compression - Automatic failover and buffering
Exercises: 1. Add Refinitiv as secondary data source 2. Implement automatic failover between vendors 3. Create data quality dashboard
Tutorial 10: Production Deployment with Monitoring¶
Level: Advanced | Time: 60 minutes
Deploy Neutryx with full observability stack.
# Setup Prometheus metrics
from neutryx.infrastructure.observability.metrics import (
MetricsCollector, register_pricing_metrics
)
metrics = MetricsCollector()
register_pricing_metrics(metrics)
# Instrument pricing function
from functools import wraps
import time
def track_pricing(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
try:
result = func(*args, **kwargs)
metrics.pricing_duration.observe(time.time() - start)
metrics.pricing_success.inc()
return result
except Exception as e:
metrics.pricing_errors.inc()
raise
return wrapper
@track_pricing
def price_option(S, K, T, r, sigma):
from neutryx.models.bs import price
return price(S, K, T, r, 0.0, sigma, "call")
# Setup distributed tracing
from neutryx.infrastructure.observability.tracing import (
init_tracer, trace_operation
)
tracer = init_tracer("neutryx-core", "jaeger", "localhost:6831")
@trace_operation(tracer, "price_portfolio")
def price_portfolio(portfolio):
with tracer.start_span("load_market_data"):
market_data = load_market_data()
with tracer.start_span("calibrate_models"):
models = calibrate_models(market_data)
with tracer.start_span("calculate_prices"):
prices = [price_instrument(inst, models) for inst in portfolio]
return prices
# Setup alerting
from neutryx.infrastructure.observability.alerting import AlertManager
alerts = AlertManager()
alerts.add_rule(
name="high_pricing_latency",
condition="pricing_duration > 1.0",
severity="warning",
notification_channels=["email", "slack"]
)
alerts.add_rule(
name="pricing_error_rate",
condition="pricing_errors / pricing_total > 0.05",
severity="critical",
notification_channels=["pagerduty", "email"]
)
# Start metrics server
from prometheus_client import start_http_server
start_http_server(8000)
print("Metrics available at http://localhost:8000/metrics")
print("Jaeger UI at http://localhost:16686")
Key Concepts: - Prometheus metrics and instrumentation - Distributed tracing with Jaeger - Grafana dashboards - Alerting and notification
Exercises: 1. Create custom Grafana dashboard 2. Implement SLA monitoring 3. Setup multi-region deployment
Interactive Examples¶
All tutorials are available as Jupyter notebooks in the examples/tutorials/ directory:
01_vanilla_pricing/- Basic option pricing02_asian_scenario/- Path-dependent options03_counterparty_cva/- XVA calculations04_heston_calibration/(coming soon)05_production_deployment/(coming soon)
Additional Resources¶
- Getting Started Guide - Basic setup and first steps
- API Reference - Complete API documentation
- Model Reference - Detailed model documentation
- Risk Controls Atlas - Risk management guide
- Performance Tuning - Optimization techniques
Next Steps¶
After completing these tutorials, you'll be ready to:
- Build production pricing systems
- Implement risk management frameworks
- Calibrate models to live market data
- Deploy scalable quantitative finance infrastructure
Continue learning with our advanced documentation and examples!