Skip to content

Chapter 146: Physics-Informed Neural Networks for the CIR Model

Chapter 146: Physics-Informed Neural Networks for the CIR Model

Overview

The Cox-Ingersoll-Ross (CIR) model is one of the most important short-rate models in quantitative finance. Unlike the Vasicek model, CIR guarantees non-negative interest rates — a critical property for realistic modeling. In this chapter, we build a Physics-Informed Neural Network (PINN) that solves the CIR bond pricing PDE, calibrates to market data (including crypto funding rates from Bybit), and applies the CIR framework to credit risk modeling.

The CIR model describes the evolution of the short rate r(t) via the stochastic differential equation:

dr = kappa * (theta - r) * dt + sigma * sqrt(r) * dW

where:

  • kappa > 0 is the mean-reversion speed (how fast rates pull back to the long-run mean)
  • theta > 0 is the long-run mean (the equilibrium rate)
  • sigma > 0 is the volatility coefficient
  • sqrt(r) makes volatility proportional to the square root of the current rate
  • dW is a standard Wiener process (Brownian motion)

The key insight: the sqrt(r) term ensures that as rates approach zero, volatility vanishes, preventing negative rates. This is guaranteed under the Feller condition:

2 * kappa * theta >= sigma^2

When this condition holds, the rate process never reaches zero.

Why CIR Over Other Short-Rate Models?

Model Comparison

FeatureVasicekHull-WhiteCIR
SDEdr = kappa(theta-r)dt + sigma dWdr = [theta(t)-ar]dt + sigma dWdr = kappa(theta-r)dt + sigma*sqrt(r) dW
Negative ratesPossiblePossibleImpossible (Feller condition)
VolatilityConstantConstant or time-varyingRate-dependent: sigma*sqrt(r)
Analytical bond pricesYesYesYes (via Bessel functions)
Transition densityNormalNormalNon-central chi-squared
Calibration complexityLowMediumMedium
Realistic for cryptoNo (negative rates meaningless)PartialYes (rates always non-negative)

Why CIR for Crypto Markets?

In cryptocurrency markets, several “interest rate-like” quantities are naturally non-negative:

  1. Funding rates: Perpetual futures funding rates on exchanges like Bybit oscillate around zero but are bounded below
  2. DeFi lending rates: Aave, Compound lending rates are always non-negative
  3. Staking yields: Ethereum staking yields are strictly positive
  4. Implied volatility mean-reversion: Crypto IV exhibits mean-reversion with level-dependent volatility

The CIR model captures these dynamics naturally.

The CIR PDE for Bond Pricing

Derivation

A zero-coupon bond with maturity T pays $1 at time T. Its price P(r, t) depends on the current short rate r and time t. Using risk-neutral pricing and Ito’s lemma, P satisfies the CIR bond pricing PDE:

dP/dt + kappa * (theta - r) * dP/dr + 0.5 * sigma^2 * r * d^2P/dr^2 - r * P = 0

with terminal condition:

P(r, T) = 1 for all r >= 0

Key difference from Hull-White PDE: Note the r multiplying the second derivative term (0.5 * sigma^2 * r * d^2P/dr^2). In the Hull-White model, this coefficient is 0.5 * sigma^2 * d^2P/dr^2 (no r multiplier). This r-dependent diffusion coefficient is what makes the CIR PDE more challenging numerically — especially near r = 0 where the diffusion coefficient vanishes.

Boundary Conditions

At r = 0:
dP/dt + kappa * theta * dP/dr = 0 (absorbing/reflecting boundary)
As r -> infinity:
P(r, t) -> 0 (high rates make bonds worthless)
At t = T:
P(r, T) = 1 (terminal payoff)

The boundary at r = 0 deserves special attention. When the Feller condition holds, r = 0 is an entrance boundary (the process never reaches it), so the PDE naturally extends. When it fails, r = 0 is a reflecting boundary, and we need the reduced PDE there.

Analytical Solution

The CIR model has a beautiful closed-form bond pricing formula:

P(r, t) = A(tau) * exp(-B(tau) * r)
where tau = T - t (time to maturity)
gamma = sqrt(kappa^2 + 2 * sigma^2)
B(tau) = 2 * (exp(gamma * tau) - 1) / ((gamma + kappa) * (exp(gamma * tau) - 1) + 2 * gamma)
A(tau) = [ 2 * gamma * exp((kappa + gamma) * tau / 2) /
((gamma + kappa) * (exp(gamma * tau) - 1) + 2 * gamma) ] ^ (2 * kappa * theta / sigma^2)

This analytical formula serves as ground truth for validating our PINN.

Yield and Forward Rate

From the bond price, we derive:

Yield:
y(r, tau) = -ln(P(r, t)) / tau = (B(tau) * r - ln(A(tau))) / tau
Instantaneous forward rate:
f(r, t, T) = -d ln(P) / dT = B'(tau) * r - A'(tau) / A(tau)

Transition Density: Non-Central Chi-Squared

The CIR process has a known transition density. Given r(s) = r_s, the distribution of r(t) for t > s is:

r(t) | r(s) = r_s ~ (sigma^2 * (1 - exp(-kappa*(t-s)))) / (4*kappa) * chi^2(df, lambda)
where:
df = 4 * kappa * theta / sigma^2 (degrees of freedom)
lambda = 4 * kappa * r_s * exp(-kappa*(t-s)) / (sigma^2 * (1 - exp(-kappa*(t-s))))
(non-centrality parameter)

This non-central chi-squared distribution is crucial for:

  • Maximum likelihood estimation (MLE) of CIR parameters
  • Simulating CIR paths exactly (no discretization error)
  • Computing option prices on bonds analytically

PINN Architecture for CIR

Network Design

The PINN takes the short rate r and time t as inputs and outputs the bond price P(r, t):

PINN Architecture for CIR Bond Pricing
========================================
INPUT LAYER
+---------------------------+
| (r, t) | 2 neurons
| r = short rate |
| t = current time |
+---------------------------+
|
v
INPUT TRANSFORMATION
+---------------------------+
| r_norm = r / r_max | Normalize to [0, 1]
| t_norm = t / T |
| sqrt_r = sqrt(r) / sqrt(r_max) <-- capture sqrt(r) structure
+---------------------------+
|
v
HIDDEN LAYERS (Fully Connected)
+---------------------------+
| Layer 1: Linear(3, 128) | 3 inputs: r_norm, t_norm, sqrt_r
| + Tanh |
+---------------------------+
|
v
+---------------------------+
| Layer 2: Linear(128, 128) |
| + Tanh |
+---------------------------+
|
v
+---------------------------+
| Layer 3: Linear(128, 128) |
| + Tanh |
+---------------------------+
|
v
+---------------------------+
| Layer 4: Linear(128, 64) |
| + Tanh |
+---------------------------+
|
v
OUTPUT LAYER
+---------------------------+
| Layer 5: Linear(64, 1) |
| + Sigmoid | Ensures P in (0, 1)
+---------------------------+

Why include sqrt(r) as an input feature? The CIR PDE has a sqrt(r) singularity structure. By providing sqrt(r) directly, the network does not need to learn this transformation internally, significantly improving convergence near r = 0.

Why Sigmoid output? Bond prices for a zero-coupon bond are always in (0, 1], so a sigmoid activation on the output layer naturally enforces this constraint.

Handling the sqrt(r) Singularity

Near r = 0, the CIR PDE becomes degenerate: the diffusion coefficient 0.5 * sigma^2 * r vanishes. This creates numerical challenges for PINNs because:

  1. The PDE residual is dominated by the drift term kappa * theta * dP/dr
  2. Small errors in d^2P/dr^2 get magnified relative to the vanishing diffusion
  3. Standard uniform collocation under-samples the critical near-zero region

Solutions implemented:

# 1. Sqrt-aware collocation: sample more points near r = 0
r_collocation = r_max * (torch.rand(N) ** 2) # quadratic sampling concentrates near 0
# 2. Weighted PDE loss: upweight residuals near r = 0
weights = 1.0 / (r_collocation + epsilon) # higher weight where r is small
# 3. Auxiliary sqrt(r) input: provide sqrt(r) as network feature
inputs = torch.stack([r, t, torch.sqrt(r + epsilon)], dim=-1)
# 4. Regularized diffusion: add small epsilon to prevent division by zero
diffusion = 0.5 * sigma**2 * (r + epsilon)

Loss Function Design

The total loss for training the CIR PINN:

L_total = w_pde * L_pde + w_bc * L_bc + w_ic * L_ic + w_analytical * L_analytical

1. PDE Residual Loss

Sample collocation points (r_i, t_i) in the interior and compute:

P = network(r, t)
# Automatic differentiation
dP_dt = autograd(P, t)
dP_dr = autograd(P, r)
d2P_dr2 = autograd(dP_dr, r)
# CIR PDE residual
residual = dP_dt + kappa * (theta - r) * dP_dr + 0.5 * sigma**2 * r * d2P_dr2 - r * P
L_pde = mean(weights * residual**2)

2. Boundary Condition Loss

# Terminal condition: P(r, T) = 1
r_terminal = sample_rates()
P_terminal = network(r_terminal, T * torch.ones_like(r_terminal))
L_ic = mean((P_terminal - 1.0)**2)
# Lower boundary: at r = 0, reduced PDE
P_at_0 = network(torch.zeros(N), t_boundary)
dP_dt_at_0 = autograd(P_at_0, t_boundary)
dP_dr_at_0 = autograd(P_at_0, r)|_{r=0} # computed via limit
L_bc_lower = mean((dP_dt_at_0 + kappa * theta * dP_dr_at_0)**2)
# Upper boundary: P -> 0 as r -> infinity
r_large = r_max * torch.ones(N)
P_at_rmax = network(r_large, t_boundary)
L_bc_upper = mean(P_at_rmax**2)

3. Analytical Constraint Loss (Optional)

Use a small set of analytically computed CIR bond prices as supervision:

r_anchor, t_anchor = generate_anchor_points()
P_analytical = cir_bond_price_analytical(r_anchor, t_anchor, kappa, theta, sigma, T)
P_predicted = network(r_anchor, t_anchor)
L_analytical = mean((P_predicted - P_analytical)**2)

This “data-driven” term helps the PINN converge faster and serves as a sanity check.

Training Strategy

Phase 1: Warm Start with Analytical Solutions (Epochs 1-500)

  • High weight on L_analytical, low weight on L_pde
  • Network learns the general shape of bond price surface
  • Learning rate: 1e-3

Phase 2: PDE-Focused Training (Epochs 501-3000)

  • Increase L_pde weight, decrease L_analytical weight
  • Network refines to satisfy the PDE exactly
  • Learning rate: 5e-4 with cosine annealing

Phase 3: Fine-tuning (Epochs 3001-5000)

  • Equal weights on all loss terms
  • Adaptive collocation: resample more points where PDE residual is large
  • Learning rate: 1e-4
# Training schedule
schedules = {
'phase1': {'epochs': 500, 'w_pde': 0.1, 'w_analytical': 10.0, 'lr': 1e-3},
'phase2': {'epochs': 2500, 'w_pde': 10.0, 'w_analytical': 1.0, 'lr': 5e-4},
'phase3': {'epochs': 2000, 'w_pde': 5.0, 'w_analytical': 2.0, 'lr': 1e-4},
}

CIR Parameter Calibration via MLE

Before training the PINN, we need CIR parameters (kappa, theta, sigma). Maximum Likelihood Estimation uses the non-central chi-squared transition density:

def cir_log_likelihood(params, rates, dt):
kappa, theta, sigma = params
ll = 0.0
c = 2 * kappa / (sigma**2 * (1 - np.exp(-kappa * dt)))
df = 4 * kappa * theta / sigma**2
for i in range(1, len(rates)):
r_prev = rates[i-1]
r_curr = rates[i]
noncentrality = 2 * c * r_prev * np.exp(-kappa * dt)
x = 2 * c * r_curr
# Log PDF of non-central chi-squared
ll += ncx2.logpdf(x, df=df, nc=noncentrality) + np.log(2 * c)
return -ll # minimize negative log-likelihood

Calibration to Bybit Funding Rates

# Fetch Bybit perpetual funding rate history
funding_rates = fetch_bybit_funding_rates("BTCUSDT", days=365)
# Ensure non-negativity (shift if needed)
rates = funding_rates['rate'].values
rates = np.maximum(rates, 1e-8) # CIR requires r > 0
# Calibrate CIR parameters
from scipy.optimize import minimize
result = minimize(
cir_log_likelihood,
x0=[1.0, 0.01, 0.1], # initial guess: kappa, theta, sigma
args=(rates, 8/24/365), # dt = 8 hours in years (Bybit funding interval)
method='L-BFGS-B',
bounds=[(0.01, 100), (1e-6, 1.0), (1e-6, 10.0)]
)
kappa, theta, sigma = result.x

Credit Risk: CIR for Default Intensity

The CIR model is widely used for modeling default intensity (hazard rate) lambda(t) in credit risk:

d(lambda) = kappa * (theta - lambda) * dt + sigma * sqrt(lambda) * dW

The survival probability (probability of no default before time T) is:

Q(t, T) = E[exp(-integral_t^T lambda(s) ds) | lambda(t)]

Remarkably, this has the exact same form as the CIR bond price:

Q(t, T) = A(T-t) * exp(-B(T-t) * lambda(t))

with the same A, B functions! Our PINN can therefore price:

  • Credit default swaps (CDS): CDS spread = (1 - recovery) * integral of default probability
  • Defaultable bonds: Price = risk-free bond price * survival probability
  • Credit options: Options on CDS spreads using CIR dynamics

PINN for Credit Risk

# The PDE for survival probability Q(lambda, t) is identical to CIR bond pricing:
# dQ/dt + kappa*(theta - lambda)*dQ/dlambda + 0.5*sigma^2*lambda*d^2Q/dlambda^2 - lambda*Q = 0
# Q(lambda, T) = 1
# We can reuse the same PINN architecture!
credit_pinn = CIRPINN(kappa=kappa, theta=theta, sigma=sigma, T=5.0)
credit_pinn.train(epochs=5000)
# CDS spread computation
def cds_spread(lambda_0, T, recovery=0.4, n_payments=4):
"""Compute par CDS spread using CIR survival probabilities."""
dt_payment = T / n_payments
protection_leg = 0.0
premium_leg = 0.0
for i in range(1, n_payments + 1):
t_i = i * dt_payment
Q_prev = credit_pinn.predict(lambda_0, t_i - dt_payment)
Q_curr = credit_pinn.predict(lambda_0, t_i)
# Protection leg: (1-R) * sum of default probability in each period
protection_leg += (1 - recovery) * (Q_prev - Q_curr) * np.exp(-r * t_i)
# Premium leg: sum of survival probability * dt
premium_leg += Q_curr * dt_payment * np.exp(-r * t_i)
return protection_leg / premium_leg

Application to Crypto Markets

Bybit Funding Rate Modeling

Perpetual futures on Bybit have a funding mechanism where longs pay shorts (or vice versa) every 8 hours. The funding rate exhibits:

  1. Mean-reversion: Rates revert to a baseline (near 0.01% per 8h in calm markets)
  2. Level-dependent volatility: Higher absolute rates have higher volatility
  3. Non-negativity: While funding can be negative, the absolute rate |funding| is always non-negative
# Fetch and model Bybit funding rates
from data_loader import fetch_bybit_funding_rates
# Get BTC and ETH funding rates
btc_funding = fetch_bybit_funding_rates("BTCUSDT", days=180)
eth_funding = fetch_bybit_funding_rates("ETHUSDT", days=180)
# CIR modeling of absolute funding rate
abs_rates = np.abs(btc_funding['rate'].values)
# Calibrate CIR
kappa, theta, sigma = calibrate_cir_mle(abs_rates, dt=8/24/365)
print(f"CIR params: kappa={kappa:.4f}, theta={theta:.6f}, sigma={sigma:.4f}")
# Train PINN for bond pricing under calibrated CIR
pinn = CIRPINN(kappa=kappa, theta=theta, sigma=sigma, T=1.0)
pinn.train(epochs=5000)
# Price a "funding rate bond" - pays 1 unit in T years
r_current = abs_rates[-1]
price = pinn.predict(r_current, 0.0) # P(r_0, 0)
print(f"Bond price (T=1yr): {price:.6f}")

Trading Strategy: Funding Rate Mean-Reversion

def funding_rate_strategy(funding_rates, cir_params, threshold=2.0):
"""
Trade based on CIR-implied fair funding rate.
When actual rate >> CIR equilibrium: go short perpetual (collect funding)
When actual rate << CIR equilibrium: go long perpetual (pay small funding)
"""
kappa, theta, sigma = cir_params
signals = []
for i, rate in enumerate(funding_rates):
# CIR expected rate after 1 funding period
dt = 8 / 24 / 365
expected_rate = theta + (rate - theta) * np.exp(-kappa * dt)
# Z-score: how far is current rate from equilibrium?
rate_std = sigma * np.sqrt(rate * (1 - np.exp(-2*kappa*dt)) / (2*kappa))
z_score = (rate - theta) / max(rate_std, 1e-8)
if z_score > threshold:
signals.append(-1) # Short: rate will revert down
elif z_score < -threshold:
signals.append(1) # Long: rate will revert up
else:
signals.append(0) # Neutral
return signals

CIR vs Vasicek vs Hull-White: Detailed Comparison

Path Simulation

import numpy as np
def simulate_cir(kappa, theta, sigma, r0, T, n_steps, n_paths):
"""Simulate CIR paths using the exact non-central chi-squared method."""
dt = T / n_steps
paths = np.zeros((n_paths, n_steps + 1))
paths[:, 0] = r0
c = sigma**2 * (1 - np.exp(-kappa * dt)) / (4 * kappa)
df = 4 * kappa * theta / sigma**2
for i in range(n_steps):
noncentrality = paths[:, i] * np.exp(-kappa * dt) / c
paths[:, i+1] = c * np.random.noncentral_chisquare(df, noncentrality)
return paths
def simulate_vasicek(kappa, theta, sigma, r0, T, n_steps, n_paths):
"""Simulate Vasicek paths (can go negative!)."""
dt = T / n_steps
paths = np.zeros((n_paths, n_steps + 1))
paths[:, 0] = r0
for i in range(n_steps):
dW = np.random.normal(0, np.sqrt(dt), n_paths)
paths[:, i+1] = paths[:, i] + kappa * (theta - paths[:, i]) * dt + sigma * dW
return paths

Comparison Results

Parameters: kappa=0.5, theta=0.05, sigma=0.1, r0=0.03, T=10
Model | Min Rate | Max Rate | Mean Rate | Std Rate | % Negative
------------|-----------|-----------|-----------|-----------|----------
CIR | 0.0012 | 0.1834 | 0.0498 | 0.0245 | 0.0%
Vasicek | -0.0856 | 0.1921 | 0.0501 | 0.0312 | 8.2%
Hull-White | -0.0743 | 0.1867 | 0.0499 | 0.0298 | 6.1%

Code Examples

Quick Start: Train CIR PINN (Python)

from python.cir_pinn import CIRPINN
from python.train import train_pinn
from python.analytical import cir_bond_price
from python.visualize import plot_bond_surface, plot_yield_curve
# CIR parameters
kappa, theta, sigma = 0.5, 0.05, 0.1
T = 5.0 # 5 years
# Create and train PINN
pinn = CIRPINN(kappa=kappa, theta=theta, sigma=sigma, T=T)
losses = train_pinn(pinn, epochs=5000, lr=1e-3)
# Compare with analytical solution
r_test = torch.linspace(0.0, 0.15, 100)
t_test = torch.zeros(100)
P_pinn = pinn.predict(r_test, t_test)
P_analytical = cir_bond_price(r_test.numpy(), 0.0, T, kappa, theta, sigma)
max_error = torch.max(torch.abs(P_pinn - torch.tensor(P_analytical)))
print(f"Max pricing error: {max_error:.6f}")
# Plot bond price surface
plot_bond_surface(pinn, r_range=(0, 0.15), T=T)

Quick Start: Rust

use pinn_cir_model::{CIRParams, cir_bond_price, simulate_cir_paths};
fn main() {
let params = CIRParams {
kappa: 0.5,
theta: 0.05,
sigma: 0.1,
r0: 0.03,
};
// Analytical bond price
let price = cir_bond_price(0.03, 5.0, &params);
println!("CIR Bond Price (r=0.03, T=5): {:.6}", price);
// Simulate paths
let paths = simulate_cir_paths(&params, 10.0, 1000, 100);
println!("Simulated {} paths with {} steps", paths.nrows(), paths.ncols());
}

Mathematical Deep Dive

The Feller Condition: 2kappatheta >= sigma^2

This condition determines the behavior at r = 0:

Case 1: 2*kappa*theta > sigma^2 (strong Feller)
- r = 0 is an entrance boundary
- The process NEVER reaches zero
- The transition density has no atom at zero
- Example: kappa=0.5, theta=0.05, sigma=0.1 => 2*0.5*0.05 = 0.05 >= 0.01 = 0.1^2 [YES]
Case 2: 2*kappa*theta = sigma^2 (critical Feller)
- r = 0 is reached but immediately reflected
- Boundary case; process touches zero with probability one but spends zero time there
Case 3: 2*kappa*theta < sigma^2 (Feller violated)
- r = 0 is reached and the process can stick at zero
- Need to specify boundary behavior (reflection)
- Common in high-volatility regimes (e.g., crypto)
- Example: kappa=0.5, theta=0.01, sigma=0.5 => 0.01 < 0.25 [VIOLATED]

Bessel Function Connection

The CIR transition density involves modified Bessel functions of the first kind:

p(r_t | r_s) = c * exp(-u - v) * (v/u)^(q/2) * I_q(2*sqrt(u*v))
where:
c = 2*kappa / (sigma^2 * (1 - exp(-kappa*(t-s))))
u = c * r_s * exp(-kappa*(t-s))
v = c * r_t
q = 2*kappa*theta/sigma^2 - 1
I_q = modified Bessel function of the first kind of order q

This density is the foundation for exact MLE calibration.

Affine Term Structure

The CIR model belongs to the affine term structure family. This means:

  • Bond prices have the form P(r,t) = A(tau) * exp(-B(tau) * r)
  • Yield curves are affine in r: y(r, tau) = a(tau) + b(tau) * r
  • This affine structure is what makes analytical solutions possible
  • Our PINN implicitly learns this affine structure

Error Analysis

Sources of PINN Error

1. Approximation error: The network has finite capacity
- Controlled by: network width and depth
- Expected: O(1/sqrt(N_params)) for well-trained networks
2. Optimization error: Training may not reach global minimum
- Controlled by: learning rate schedule, training epochs
- Mitigated by: warm start with analytical solution
3. Collocation error: Finite sampling of PDE residual points
- Controlled by: number of collocation points and their distribution
- Mitigated by: sqrt(r)-aware sampling near r = 0
4. Boundary condition error: Imperfect enforcement of BC/IC
- Controlled by: loss weights and dedicated boundary sampling
- Mitigated by: hard constraint via output transformation

Typical Performance

Configuration: 4 hidden layers, 128 neurons each, 10000 collocation points
Metric | Value
--------------------------|--------
Max absolute error | ~1e-4
Mean absolute error | ~2e-5
Relative error (%) | ~0.02%
PDE residual (L2) | ~1e-6
Training time (5000 eps) | ~120 sec (GPU)
Inference time (1 point) | ~0.1 ms

File Structure

146_pinn_cir_model/
|-- README.md # This file
|-- README.ru.md # Russian translation
|-- readme.simple.md # Simple explanation (English)
|-- readme.simple.ru.md # Simple explanation (Russian)
|-- python/
| |-- __init__.py # Package initialization
| |-- requirements.txt # Python dependencies
| |-- cir_pinn.py # PINN architecture for CIR PDE
| |-- train.py # Training loop with sqrt(r) collocation
| |-- data_loader.py # Rate data: treasury + Bybit funding
| |-- analytical.py # CIR bond pricing, transition density
| |-- calibration.py # MLE calibration of CIR parameters
| |-- credit_risk.py # CIR for default intensity modeling
| |-- visualize.py # Plotting: rate paths, bond surfaces
| |-- backtest.py # Funding rate trading strategy
|-- rust_pinn_cir/
| |-- Cargo.toml # Rust project configuration
| |-- src/
| | |-- lib.rs # Core CIR library
| | |-- bin/
| | |-- train.rs # PINN training binary
| | |-- price_bonds.rs # Bond pricing binary
| | |-- fetch_data.rs # Bybit data fetcher
| |-- examples/
| |-- basic_pricing.rs # Simple CIR pricing example

References

  1. Cox, J.C., Ingersoll, J.E., Ross, S.A. (1985). “A Theory of the Term Structure of Interest Rates.” Econometrica, 53(2), 385-407.
  2. Feller, W. (1951). “Two Singular Diffusion Problems.” Annals of Mathematics, 54(1), 173-182.
  3. Raissi, M., Perdikaris, P., Karniadakis, G.E. (2019). “Physics-Informed Neural Networks.” Journal of Computational Physics, 378, 686-707.
  4. Brigo, D., Mercurio, F. (2006). Interest Rate Models - Theory and Practice. Springer.
  5. Duffie, D., Singleton, K.J. (2003). Credit Risk. Princeton University Press.
  6. Chen, R.R., Scott, L. (1992). “Pricing Interest Rate Options in a Two-Factor Cox-Ingersoll-Ross Model of the Term Structure.” Review of Financial Studies, 5(4), 613-636.

Running the Code

Python

Terminal window
cd 146_pinn_cir_model/python
pip install -r requirements.txt
# Train the PINN
python -m python.train --epochs 5000 --kappa 0.5 --theta 0.05 --sigma 0.1
# Fetch Bybit data and calibrate
python -m python.data_loader --symbol BTCUSDT --days 180
# Run backtest
python -m python.backtest --symbol BTCUSDT --days 90

Rust

Terminal window
cd 146_pinn_cir_model/rust_pinn_cir
# Build
cargo build --release
# Run training
cargo run --release --bin train
# Price bonds
cargo run --release --bin price_bonds
# Fetch Bybit data
cargo run --release --bin fetch_data -- --symbol BTCUSDT --days 180
# Run example
cargo run --release --example basic_pricing