User Guides¶
Comprehensive guides for using JACTUS to model financial contracts.
Getting Started¶
Introduction to JACTUS¶
JACTUS is a JAX-based implementation of the ACTUS financial contract standard. It provides:
Type-safe contract modeling with full validation
JAX integration for automatic differentiation and GPU acceleration
18 contract types covering principal, non-principal, and derivative instruments
Event-driven simulation for cash flow generation
Composable contracts for complex financial structures
Learning Resources¶
Interactive Notebooks
For hands-on learning with visualizations, explore the Jupyter notebooks:
examples/notebooks/01_annuity_mortgage.ipynb- 30-year mortgage with amortization chartsexamples/notebooks/02_options_contracts.ipynb- Call/Put options with payoff diagramsexamples/notebooks/03_interest_rate_cap.ipynb- Interest rate protection scenariosexamples/notebooks/04_stock_commodity.ipynb- Stock and commodity position tracking
Python Examples
Ready-to-run Python scripts are available in examples/:
pam_example.py- Principal at Maturity contractslam_example.py- Linear Amortizer contractsinterest_rate_swap_example.py- Plain vanilla swapsfx_swap_example.py- FX swapscross_currency_basis_swap_example.py- Cross-currency swaps
Basic Workflow¶
The typical workflow for using JACTUS:
Define Contract Attributes:
from jactus.core import ContractAttributes, ContractType, ContractRole, ActusDateTime attrs = ContractAttributes( contract_id="LOAN-001", contract_type=ContractType.PAM, contract_role=ContractRole.RPA, # Receive principal, pay interest status_date=ActusDateTime(2024, 1, 1, 0, 0, 0), # ... more attributes )
Create Risk Factor Observer:
from jactus.observers import ConstantRiskFactorObserver rf_observer = ConstantRiskFactorObserver(constant_value=0.05) # 5% interest rate
Create Contract:
from jactus.contracts import create_contract contract = create_contract(attrs, rf_observer)
Run Simulation:
result = contract.simulate() for event in result.events: print(f"{event.event_type}: {event.event_time} -> ${event.payoff:.2f}")
Principal Contracts Guide¶
PAM - Principal at Maturity¶
Principal at Maturity contracts are interest-only loans where principal is repaid at maturity.
Use Cases:
Interest-only mortgages
Bullet bonds
Short-term loans
Treasury securities
Example: 30-Year Mortgage:
from jactus.core import (
ContractAttributes, ContractType, ContractRole, ActusDateTime,
DayCountConvention, EndOfMonthConvention
)
from jactus.contracts import create_contract
from jactus.observers import ConstantRiskFactorObserver
# Define mortgage attributes
attrs = ContractAttributes(
contract_id="MORTGAGE-001",
contract_type=ContractType.PAM,
contract_role=ContractRole.RPA, # Lender perspective
status_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
initial_exchange_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
maturity_date=ActusDateTime(2054, 1, 1, 0, 0, 0), # 30 years
notional_principal=500_000.0, # $500k loan
nominal_interest_rate=0.06, # 6% annual
day_count_convention=DayCountConvention.A360,
interest_payment_cycle="1M", # Monthly payments
interest_payment_anchor=ActusDateTime(2024, 2, 1, 0, 0, 0),
end_of_month_convention=EndOfMonthConvention.EOM,
currency="USD",
)
# Create observer with fixed rate
rf_observer = ConstantRiskFactorObserver(constant_value=0.06)
# Create contract and simulate
contract = create_contract(attrs, rf_observer)
result = contract.simulate()
# Analyze results
total_interest = sum(event.payoff for event in result.events
if event.event_type.name == "IP")
print(f"Total interest over 30 years: ${total_interest:,.2f}")
Key Parameters:
notional_principal: Loan amountnominal_interest_rate: Annual interest rateinterest_payment_cycle: Payment frequency (1M=monthly, 3M=quarterly, 1Y=annual)maturity_date: When principal is repaid
See: examples/pam_example.py for complete examples
LAM - Linear Amortizer¶
Linear amortizer contracts have fixed principal payments plus decreasing interest.
Use Cases:
Auto loans
Equipment financing
Fixed principal amortization schedules
Construction loans
Example: 5-Year Auto Loan:
attrs = ContractAttributes(
contract_id="AUTO-001",
contract_type=ContractType.LAM,
contract_role=ContractRole.RPA,
status_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
initial_exchange_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
maturity_date=ActusDateTime(2029, 1, 1, 0, 0, 0), # 5 years
notional_principal=30_000.0, # $30k car loan
nominal_interest_rate=0.048, # 4.8% APR
interest_payment_cycle="1M", # Monthly payments
interest_payment_anchor=ActusDateTime(2024, 2, 1, 0, 0, 0),
principal_redemption_cycle="1M", # Monthly principal payments
principal_redemption_anchor=ActusDateTime(2024, 2, 1, 0, 0, 0),
next_principal_redemption_amount=500.0, # $500/month principal
currency="USD",
)
Amortization Modes:
NT(Notional): Principal amount per periodNTIED(Notional IED): Based on initial notionalNTL(Notional Last): Based on remaining notional
See: examples/lam_example.py for complete examples
Derivative Contracts Guide¶
Interest Rate Swaps (SWPPV)¶
Plain vanilla interest rate swaps exchange fixed and floating rate payments.
Use Cases:
Convert fixed-rate debt to floating
Convert floating-rate debt to fixed
Interest rate risk management
Speculation on rate movements
Example: 5-Year Fixed vs Floating Swap:
attrs = ContractAttributes(
contract_id="IRS-001",
contract_type=ContractType.SWPPV,
contract_role=ContractRole.RFL, # Pay fixed, receive floating
status_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
maturity_date=ActusDateTime(2029, 1, 1, 0, 0, 0),
notional_principal=10_000_000.0, # $10M notional
nominal_interest_rate=0.05, # 5% fixed rate
nominal_interest_rate_2=0.03, # 3% initial floating rate
interest_payment_cycle="3M", # Quarterly payments
interest_payment_anchor=ActusDateTime(2024, 1, 1, 0, 0, 0),
rate_reset_cycle="3M", # Quarterly rate resets
rate_reset_anchor=ActusDateTime(2024, 1, 1, 0, 0, 0),
delivery_settlement="D", # Net settlement
currency="USD",
)
Settlement Options:
D(Net): Only pay/receive the differenceS(Gross): Exchange both payments
See: examples/interest_rate_swap_example.py for detailed example
FX Derivatives (FXOUT)¶
Foreign exchange outright contracts for currency exchange at future dates.
Use Cases:
FX hedging
Forward FX contracts
Currency swaps
International payment hedging
Example: EUR/USD FX Swap:
attrs = ContractAttributes(
contract_id="FX-001",
contract_type=ContractType.FXOUT,
contract_role=ContractRole.RPA,
status_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
maturity_date=ActusDateTime(2025, 1, 1, 0, 0, 0),
settlement_date=ActusDateTime(2025, 1, 1, 0, 0, 0),
currency="EUR", # Primary currency
currency_2="USD", # Secondary currency
notional_principal=1_000_000.0, # EUR 1M
notional_principal_2=1_100_000.0, # USD 1.1M (at spot)
delivery_settlement="S", # Gross settlement
purchase_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
price_at_purchase_date=1.10, # Spot rate
)
# Forward rate observer
rf_observer = ConstantRiskFactorObserver(constant_value=1.12) # Forward rate
See: examples/fx_swap_example.py for complete example
Options (OPTNS)¶
Option contracts provide the right (not obligation) to buy/sell at a strike price.
Types:
Call options (right to buy)
Put options (right to sell)
European (exercise at maturity only)
American (exercise anytime)
Example: European Call Option:
attrs = ContractAttributes(
contract_id="OPT-001",
contract_type=ContractType.OPTNS,
contract_role=ContractRole.RPA,
status_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
maturity_date=ActusDateTime(2025, 1, 1, 0, 0, 0),
option_type="C", # Call option
option_strike_1=105_000.0, # Strike price
option_exercise_type="E", # European
settlement_currency="USD",
currency="USD",
)
Option Parameters:
option_type: “C” (call) or “P” (put)option_exercise_type: “E” (European) or “A” (American)option_strike_1: Primary strike priceoption_strike_2: Secondary strike (for spreads)
Contract Composition Guide¶
Cross-Currency Basis Swaps (SWAPS)¶
Generic swaps support multi-leg, multi-currency structures.
Use Cases:
Cross-currency basis swaps
Multi-currency debt management
Foreign subsidiary funding
Basis trading
Example: EUR vs USD Basis Swap:
from jactus.observers import MockChildContractObserver
# Create child leg contracts (EUR and USD legs)
eur_leg = create_contract(eur_leg_attrs, eur_rf_observer)
usd_leg = create_contract(usd_leg_attrs, usd_rf_observer)
# Register child contracts
child_observer = MockChildContractObserver()
child_observer.register_child(
"EUR-LEG",
events=list(eur_leg.generate_event_schedule().events),
state=eur_leg.initialize_state(),
attributes={"notional_principal": 10_000_000.0}
)
child_observer.register_child(
"USD-LEG",
events=list(usd_leg.generate_event_schedule().events),
state=usd_leg.initialize_state(),
attributes={"notional_principal": 11_000_000.0}
)
# Create parent swap contract
swap_attrs = ContractAttributes(
contract_id="XCCY-001",
contract_type=ContractType.SWAPS,
contract_role=ContractRole.RPA,
status_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
maturity_date=ActusDateTime(2029, 1, 1, 0, 0, 0),
contract_structure='{"FirstLeg": "EUR-LEG", "SecondLeg": "USD-LEG"}',
delivery_settlement="D",
currency="USD",
)
swap = create_contract(swap_attrs, rf_observer, child_observer)
Composite Contract Types:
SWAPS: Multi-leg swaps
CAPFL: Caps/floors on underlier
CEG: Credit guarantees on covered contracts
CEC: Collateral tracking for covered contracts
See: examples/cross_currency_basis_swap_example.py for detailed example
Credit Enhancement (CEG, CEC)¶
Credit enhancement contracts provide protection and collateral management.
CEG - Credit Enhancement Guarantee:
Provides credit protection on covered contracts:
attrs = ContractAttributes(
contract_id="CEG-001",
contract_type=ContractType.CEG,
contract_role=ContractRole.RPA,
status_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
maturity_date=ActusDateTime(2029, 1, 1, 0, 0, 0),
contract_structure='{"CoveredContract": "LOAN-001"}',
coverage=0.8, # 80% coverage
credit_event_type="DL", # Default
credit_enhancement_guarantee_extent="NO", # Notional only
currency="USD",
)
CEC - Credit Enhancement Collateral:
Tracks collateral against exposure:
attrs = ContractAttributes(
contract_id="CEC-001",
contract_type=ContractType.CEC,
contract_role=ContractRole.RPA,
status_date=ActusDateTime(2024, 1, 1, 0, 0, 0),
maturity_date=ActusDateTime(2029, 1, 1, 0, 0, 0),
contract_structure='{"CoveredContract": "LOAN-001", "CoveringContract": "STK-001"}',
coverage=1.2, # 120% collateral requirement
credit_enhancement_guarantee_extent="NO",
currency="USD",
)
Schedule Generation Guide¶
Understanding Cycles¶
ACTUS uses cycle notation for recurring events:
1D: Daily1W: Weekly1M: Monthly3M: Quarterly6M: Semi-annual1Y: Annual
Stub Periods:
+: Short stub at end-: Short stub at beginning
Example:
3M+: Quarterly with short final period1M-: Monthly with short initial period
Business Day Conventions¶
Handle non-business days:
SCF: Shift calendar followingSCMF: Shift calendar modified followingCSF: Calculate shift followingCSMF: Calculate shift modified following
Example:
If payment falls on Saturday, SCF moves to Monday.
Day Count Conventions¶
Calculate interest accrual:
A360: Actual/360A365: Actual/36530E360: 30E/360 (European)AA: Actual/Actual
Advanced Topics¶
JAX Integration¶
JACTUS supports JAX automatic differentiation for risk analytics:
import jax
import jax.numpy as jnp
from jactus.observers import JaxRiskFactorObserver
def contract_pv(rate):
attrs = ContractAttributes(
contract_id="LOAN",
contract_type=ContractType.PAM,
contract_role=ContractRole.RPA,
status_date=ActusDateTime(2024, 1, 1),
initial_exchange_date=ActusDateTime(2024, 1, 15),
maturity_date=ActusDateTime(2025, 1, 15),
notional_principal=100_000.0,
nominal_interest_rate=rate,
interest_payment_cycle="6M",
day_count_convention="30E360",
)
observer = JaxRiskFactorObserver(jnp.array([rate]))
result = create_contract(attrs, observer).simulate()
return sum(e.payoff for e in result.events)
dv01 = jax.grad(contract_pv)(0.05) * 0.0001
print(f"DV01: ${dv01:,.2f}")
Array-Mode Portfolio API¶
For batch simulation of large portfolios, use the array-mode API which runs JIT-compiled JAX kernels over batched arrays (12 of 18 contract types supported):
from jactus.contracts.portfolio import simulate_portfolio
# contracts = list of (ContractAttributes, RiskFactorObserver) tuples
results = simulate_portfolio(contracts)
See Array-Mode Simulation & Portfolio API for full details, vmap patterns, and GPU benchmarks.
Performance Optimization¶
Tips for optimal performance:
Use array-mode for portfolios (
simulate_portfolio())Use JIT compilation for repeated calculations
Use constant observers when rates don’t change
Enable float64 before importing JACTUS for precision-sensitive analytics
Custom Risk Factor Observers¶
Implement custom observers by subclassing BaseRiskFactorObserver:
from jactus.observers.risk_factor import BaseRiskFactorObserver
class YieldCurveObserver(BaseRiskFactorObserver):
def __init__(self, curve):
super().__init__(name="yield_curve")
self.curve = curve
def _get_risk_factor(self, identifier, time, *args, **kwargs):
# Interpolate rate from yield curve
return self.curve.get_rate(time)
def _get_event_data(self, identifier, time, *args, **kwargs):
return None
Behavioral Risk Factor Observers¶
JACTUS distinguishes between two categories of risk factor observers:
Market observers return values based solely on an identifier and time (e.g., interest rate curves, FX rates). They are stateless with respect to the contract.
Behavioral observers are aware of the contract’s internal state (notional, interest rate, loan age, performance status) and can dynamically inject events into the simulation timeline. They model borrower or depositor behavior that depends on the contract’s current condition.
Behavioral observers implement the BehaviorRiskFactorObserver protocol, which
extends the standard risk factor observer with a contract_start() method. This
method inspects the contract attributes and returns a list of CalloutEvent
objects that are merged into the simulation schedule. At each callout time, the
simulation engine evaluates the behavioral observer with the current contract state.
Built-in behavioral observers:
PrepaymentSurfaceObserver– Models prepayment rates as a function of interest rate spread and loan age using a 2D interpolation surface. Returns a Multiplicative Reduction Delta (MRD) that reduces the outstanding notional.DepositTransactionObserver– Models deposit inflows and outflows for UMP (Undefined Maturity Profile) contracts. Returns an Absolute Funded Delta (AFD) representing the change in deposit balance.
Example: Prepayment model with a PAM contract
Define a 2D prepayment surface and attach it to a PAM contract:
import jax.numpy as jnp
from jactus.core import (
ContractAttributes, ContractType, ContractRole, ActusDateTime,
)
from jactus.contracts import create_contract
from jactus.observers import ConstantRiskFactorObserver
from jactus.utilities.surface import Surface2D
from jactus.observers.prepayment import PrepaymentSurfaceObserver
# Define a prepayment surface: spread (rows) x loan age in years (columns)
surface = Surface2D(
x_margins=jnp.array([-2.0, 0.0, 1.0, 2.0, 3.0]), # spread %
y_margins=jnp.array([0.0, 1.0, 2.0, 3.0, 5.0, 10.0]), # age years
values=jnp.array([
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # spread=-2%
[0.00, 0.00, 0.01, 0.00, 0.00, 0.00], # spread= 0%
[0.00, 0.01, 0.02, 0.00, 0.00, 0.00], # spread= 1%
[0.00, 0.02, 0.05, 0.03, 0.005, 0.00], # spread= 2%
[0.01, 0.05, 0.10, 0.07, 0.02, 0.00], # spread= 3%
]),
)
# Create prepayment observer with semi-annual observations
prepayment_observer = PrepaymentSurfaceObserver(
surface=surface,
fixed_market_rate=0.04, # Current market rate for spread calculation
prepayment_cycle="6M", # Observe every 6 months
model_id="ppm01",
)
# Define PAM contract
attrs = ContractAttributes(
contract_id="MORTGAGE-001",
contract_type=ContractType.PAM,
contract_role=ContractRole.RPA,
status_date=ActusDateTime(2024, 1, 1),
initial_exchange_date=ActusDateTime(2024, 1, 15),
maturity_date=ActusDateTime(2034, 1, 15),
notional_principal=500_000.0,
nominal_interest_rate=0.065,
interest_payment_cycle="1M",
interest_payment_anchor=ActusDateTime(2024, 2, 15),
currency="USD",
)
# Create contract and simulate with behavioral observer
rf_observer = ConstantRiskFactorObserver(constant_value=0.0)
contract = create_contract(attrs, rf_observer)
result = contract.simulate(behavior_observers=[prepayment_observer])
# Callout events inject PP (Principal Prepayment) observations into the timeline
for event in result.events:
print(f"{event.event_type}: {event.event_time} -> ${event.payoff:.2f}")
Scenario Management¶
The Scenario class bundles market and behavioral observers into a single,
named, reusable simulation configuration. This enables easy comparison between
different simulation environments (e.g., base case vs. stress scenarios).
Example: Creating and using a Scenario:
from jactus.observers import ConstantRiskFactorObserver
from jactus.observers.scenario import Scenario
from jactus.observers.prepayment import PrepaymentSurfaceObserver
# Base case: low rates, moderate prepayment
base_scenario = Scenario(
scenario_id="base-case",
description="Stable rates with moderate prepayment behavior",
market_observers={
"rates": ConstantRiskFactorObserver(constant_value=0.04),
},
behavior_observers={
"prepayment": prepayment_observer,
},
)
# Stress scenario: rising rates, no prepayment
stress_scenario = Scenario(
scenario_id="rising-rates",
description="Rising rate environment, no prepayment",
market_observers={
"rates": ConstantRiskFactorObserver(constant_value=0.07),
},
behavior_observers={},
)
# Run both scenarios and compare
base_result = contract.simulate(scenario=base_scenario)
stress_result = contract.simulate(scenario=stress_scenario)
print(f"Base case net cashflow: {sum(e.payoff for e in base_result.events):,.2f}")
print(f"Stress case net cashflow: {sum(e.payoff for e in stress_result.events):,.2f}")
A scenario provides two key methods:
get_observer()– Returns a unifiedRiskFactorObservercomposing all market observers, suitable for passing to the simulation engine.get_callout_events(attributes)– Collects and merges callout events from all behavioral observers, sorted by time.
Callout Event Integration in BaseContract.simulate()
The simulate() method on BaseContract natively supports behavioral observers.
When behavioral observers are provided (via a Scenario, the behavior_observers
parameter, or as the risk_factor_observer itself), the simulation engine:
Calls
contract_start()on each behavioral observer to collect callout eventsMerges callout events into the scheduled event timeline
At each callout time, evaluates the behavioral observer with the current state
This means no special setup is required beyond creating the behavioral observer and
passing it to simulate().
CLI Guide¶
JACTUS ships a jactus CLI (auto-installed with pip). Outputs rich tables
in TTY, JSON when piped.
Exploring Contracts¶
# List all 18 contract types
jactus contract list
# Get schema for a specific type
jactus contract schema --type PAM
# List available observers
jactus observer list
Simulating¶
# Simulate from JSON attributes
jactus simulate --type PAM --attrs '{
"contract_id": "LOAN-001",
"status_date": "2024-01-01",
"contract_role": "RPA",
"initial_exchange_date": "2024-01-15",
"maturity_date": "2025-01-15",
"notional_principal": 100000,
"nominal_interest_rate": 0.05,
"interest_payment_cycle": "6M",
"day_count_convention": "30E360"
}'
# Validate before simulating
jactus contract validate --type PAM --attrs loan.json
Risk Analytics¶
# DV01 (dollar value of a basis point)
jactus risk dv01 --type PAM --attrs loan.json
# Duration, convexity, and full sensitivities
jactus risk sensitivities --type PAM --attrs loan.json
Portfolio Management¶
# Simulate a portfolio
jactus portfolio simulate --file portfolio.json
# Aggregate cash flows by frequency
jactus portfolio aggregate --file portfolio.json --frequency quarterly
Output Formats¶
# JSON output for pipelines
jactus simulate --type PAM --attrs loan.json --output json | jq '.summary'
# CSV output, non-zero events only
jactus simulate --type PAM --attrs loan.json --output csv --nonzero
MCP Server¶
JACTUS includes an MCP (Model Context Protocol) server for integration with AI assistants like Claude Code and Claude Desktop.
Installation¶
pip install git+https://github.com/pedronahum/JACTUS.git#subdirectory=tools/mcp-server
The MCP server provides tools for:
Listing and discovering contract types
Getting contract schemas with working examples
Validating contract attributes
Simulating contracts
Computing risk metrics (DV01, delta, gamma)
Simulating portfolios
Searching documentation
For Claude Code, place .mcp.json in the project root to enable
auto-discovery when opening the JACTUS workspace.
See Also¶
API Reference - Complete API reference
Array-Mode Simulation & Portfolio API - Array-mode portfolio API for GPU/TPU
Derivative Contracts in JACTUS - Derivative contracts guide
JACTUS Architecture Guide - System architecture
PAM Contract: A Complete Architecture Walkthrough - Deep dive into PAM implementation
Additional Resources¶
Working Examples:
examples/pam_example.py- PAM mortgages and bondsexamples/lam_example.py- Amortizing loansexamples/interest_rate_swap_example.py- Interest rate swapsexamples/fx_swap_example.py- FX swapsexamples/cross_currency_basis_swap_example.py- Cross-currency swaps
Documentation:
docs/ARCHITECTURE.md- High-level architecturedocs/ARRAY_MODE.md- Array-mode portfolio APIdocs/PAM.md- PAM implementation walkthroughdocs/derivatives.md- All derivative contract types
External Resources: