.. _user-guides: 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 charts * ``examples/notebooks/02_options_contracts.ipynb`` - Call/Put options with payoff diagrams * ``examples/notebooks/03_interest_rate_cap.ipynb`` - Interest rate protection scenarios * ``examples/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 contracts * ``lam_example.py`` - Linear Amortizer contracts * ``interest_rate_swap_example.py`` - Plain vanilla swaps * ``fx_swap_example.py`` - FX swaps * ``cross_currency_basis_swap_example.py`` - Cross-currency swaps Basic Workflow ^^^^^^^^^^^^^^ The typical workflow for using JACTUS: 1. **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 ) 2. **Create Risk Factor Observer**:: from jactus.observers import ConstantRiskFactorObserver rf_observer = ConstantRiskFactorObserver(constant_value=0.05) # 5% interest rate 3. **Create Contract**:: from jactus.contracts import create_contract contract = create_contract(attrs, rf_observer) 4. **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 amount * ``nominal_interest_rate``: Annual interest rate * ``interest_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 period * ``NTIED`` (Notional IED): Based on initial notional * ``NTL`` (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 difference * ``S`` (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 price * ``option_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``: Daily * ``1W``: Weekly * ``1M``: Monthly * ``3M``: Quarterly * ``6M``: Semi-annual * ``1Y``: Annual **Stub Periods:** * ``+``: Short stub at end * ``-``: Short stub at beginning **Example:** * ``3M+``: Quarterly with short final period * ``1M-``: Monthly with short initial period Business Day Conventions ^^^^^^^^^^^^^^^^^^^^^^^^^ Handle non-business days: * ``SCF``: Shift calendar following * ``SCMF``: Shift calendar modified following * ``CSF``: Calculate shift following * ``CSMF``: Calculate shift modified following **Example:** If payment falls on Saturday, ``SCF`` moves to Monday. Day Count Conventions ^^^^^^^^^^^^^^^^^^^^^ Calculate interest accrual: * ``A360``: Actual/360 * ``A365``: Actual/365 * ``30E360``: 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): .. code-block:: python from jactus.contracts.portfolio import simulate_portfolio # contracts = list of (ContractAttributes, RiskFactorObserver) tuples results = simulate_portfolio(contracts) See :doc:`../ARRAY_MODE` for full details, ``vmap`` patterns, and GPU benchmarks. Performance Optimization ^^^^^^^^^^^^^^^^^^^^^^^^ Tips for optimal performance: 1. **Use array-mode** for portfolios (``simulate_portfolio()``) 2. **Use JIT compilation** for repeated calculations 3. **Use constant observers** when rates don't change 4. **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 unified ``RiskFactorObserver`` composing 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: 1. Calls ``contract_start()`` on each behavioral observer to collect callout events 2. Merges callout events into the scheduled event timeline 3. 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 -------- * :doc:`../api/index` - Complete API reference * :doc:`../ARRAY_MODE` - Array-mode portfolio API for GPU/TPU * :doc:`../derivatives` - Derivative contracts guide * :doc:`../ARCHITECTURE` - System architecture * :doc:`../PAM` - Deep dive into PAM implementation * `Examples Directory `_ Additional Resources -------------------- **Working Examples:** * ``examples/pam_example.py`` - PAM mortgages and bonds * ``examples/lam_example.py`` - Amortizing loans * ``examples/interest_rate_swap_example.py`` - Interest rate swaps * ``examples/fx_swap_example.py`` - FX swaps * ``examples/cross_currency_basis_swap_example.py`` - Cross-currency swaps **Documentation:** * ``docs/ARCHITECTURE.md`` - High-level architecture * ``docs/ARRAY_MODE.md`` - Array-mode portfolio API * ``docs/PAM.md`` - PAM implementation walkthrough * ``docs/derivatives.md`` - All derivative contract types **External Resources:** * `ACTUS Standard `_ * `JAX Documentation `_ * `GitHub Repository `_