diff --git a/notebooks/07_risk_tranches.ipynb b/notebooks/07_risk_tranches.ipynb new file mode 100644 index 0000000..b147da2 --- /dev/null +++ b/notebooks/07_risk_tranches.ipynb @@ -0,0 +1,409 @@ +{ + "nbformat": 4, + "nbformat_minor": 4, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11.0" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Risk Tranche System\n", + "\n", + "Explores the three-tranche risk layering system in MycoFi:\n", + "\n", + "- **myUSD-S (Senior)**: Lowest risk, first collateral claim, 3% APY target\n", + "- **myUSD-M (Mezzanine)**: Medium risk, second claim, 8% APY target\n", + "- **$MYCO (Junior/Equity)**: Highest risk, residual claim, absorbs volatility\n", + "\n", + "Yield waterfall: staking rewards flow Senior → Mezzanine → Junior.\n", + "Loss waterfall: losses absorbed Junior → Mezzanine → Senior." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.patches as mpatches\n", + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath('..'))\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams['figure.figsize'] = (14, 5)\n", + "plt.rcParams['figure.dpi'] = 100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. System Setup\n", + "\n", + "Create a `TrancheParams` and `RiskTrancheSystem`, deposit \\$1M collateral, then mint all three tranches." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from src.primitives.risk_tranching import (\n", + " TrancheParams,\n", + " RiskTrancheSystem,\n", + " TrancheState,\n", + " deposit_collateral,\n", + " mint_tranche,\n", + " mint_capacity,\n", + " distribute_yield,\n", + " apply_loss,\n", + " check_liquidation,\n", + " get_tranche_metrics,\n", + ")\n", + "\n", + "# Instantiate system\n", + "params = TrancheParams(\n", + " senior_collateral_ratio=1.5,\n", + " mezzanine_collateral_ratio=1.2,\n", + " senior_yield_target=0.03,\n", + " mezzanine_yield_target=0.08,\n", + " max_senior_fraction=0.50,\n", + " max_mezzanine_fraction=0.30,\n", + ")\n", + "\n", + "from dataclasses import field\n", + "\n", + "system = RiskTrancheSystem(\n", + " params=params,\n", + " senior=TrancheState(name=\"myUSD-S\"),\n", + " mezzanine=TrancheState(name=\"myUSD-M\"),\n", + " junior=TrancheState(name=\"$MYCO\"),\n", + ")\n", + "\n", + "# Deposit $1M collateral\n", + "system = deposit_collateral(system, 1_000_000.0)\n", + "print(f\"Total collateral after deposit: ${system.total_collateral:,.0f}\")\n", + "\n", + "# Check mint capacity\n", + "caps = mint_capacity(system)\n", + "print(f\"\\nMint capacity:\")\n", + "for tranche, cap in caps.items():\n", + " print(f\" {tranche:12s}: ${cap:,.0f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Mint all three tranches\n", + "system, senior_minted = mint_tranche(system, \"senior\", 300_000.0)\n", + "system, mez_minted = mint_tranche(system, \"mezzanine\", 150_000.0)\n", + "system, junior_minted = mint_tranche(system, \"junior\", 100_000.0)\n", + "\n", + "print(f\"Minted:\")\n", + "print(f\" myUSD-S (Senior): ${senior_minted:>12,.0f}\")\n", + "print(f\" myUSD-M (Mezzanine): ${mez_minted:>12,.0f}\")\n", + "print(f\" $MYCO (Junior): ${junior_minted:>12,.0f}\")\n", + "\n", + "metrics = get_tranche_metrics(system)\n", + "print(f\"\\nSystem collateral ratio: {metrics['system_cr']:.3f}\")\n", + "print(f\"Senior CR: {metrics['senior']['cr']:.3f}\")\n", + "print(f\"Mezzanine CR: {metrics['mezzanine']['cr']:.3f}\")\n", + "print(f\"Junior CR: {metrics['junior']['cr']:.3f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 365-Day Yield Simulation\n", + "\n", + "Simulate 365 daily steps with a 4% staking APY applied to total collateral.\n", + "Record tranche supplies, collateral ratios, and cumulative yield at each step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import copy\n", + "\n", + "DAYS = 365\n", + "DT = 1.0 / 365 # One day in years\n", + "STAKING_APY = 0.04 # 4% annual staking yield\n", + "\n", + "# Snapshot function\n", + "def snapshot(sys, day):\n", + " m = get_tranche_metrics(sys)\n", + " return {\n", + " \"day\": day,\n", + " \"total_collateral\": sys.total_collateral,\n", + " \"system_cr\": m[\"system_cr\"],\n", + " \"senior_supply\": m[\"senior\"][\"supply\"],\n", + " \"senior_cr\": m[\"senior\"][\"cr\"],\n", + " \"senior_yield\": m[\"senior\"][\"yield\"],\n", + " \"mez_supply\": m[\"mezzanine\"][\"supply\"],\n", + " \"mez_cr\": m[\"mezzanine\"][\"cr\"],\n", + " \"mez_yield\": m[\"mezzanine\"][\"yield\"],\n", + " \"junior_supply\": m[\"junior\"][\"supply\"],\n", + " \"junior_cr\": m[\"junior\"][\"cr\"],\n", + " \"junior_yield\": m[\"junior\"][\"yield\"],\n", + " }\n", + "\n", + "# Deep-copy system so we can reuse the initial state for the stress test\n", + "sim_system = copy.deepcopy(system)\n", + "history = [snapshot(sim_system, 0)]\n", + "\n", + "for day in range(1, DAYS + 1):\n", + " daily_yield = sim_system.total_collateral * STAKING_APY * DT\n", + " sim_system = distribute_yield(sim_system, daily_yield, DT)\n", + " history.append(snapshot(sim_system, day))\n", + "\n", + "days = [h[\"day\"] for h in history]\n", + "senior_sup = [h[\"senior_supply\"] for h in history]\n", + "mez_sup = [h[\"mez_supply\"] for h in history]\n", + "junior_sup = [h[\"junior_supply\"] for h in history]\n", + "senior_cr = [h[\"senior_cr\"] for h in history]\n", + "mez_cr = [h[\"mez_cr\"] for h in history]\n", + "junior_cr = [h[\"junior_cr\"] for h in history]\n", + "system_cr = [h[\"system_cr\"] for h in history]\n", + "senior_yield = [h[\"senior_yield\"] for h in history]\n", + "mez_yield = [h[\"mez_yield\"] for h in history]\n", + "junior_yield = [h[\"junior_yield\"] for h in history]\n", + "\n", + "print(f\"Final day 365 cumulative yield:\")\n", + "print(f\" Senior: ${senior_yield[-1]:>12,.0f}\")\n", + "print(f\" Mezzanine: ${mez_yield[-1]:>12,.0f}\")\n", + "print(f\" Junior: ${junior_yield[-1]:>12,.0f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Visualizations — Normal Operation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n", + "\n", + "# --- Plot 1: Tranche supplies over time ---\n", + "ax = axes[0]\n", + "ax.stackplot(days, senior_sup, mez_sup, junior_sup,\n", + " labels=['myUSD-S (Senior)', 'myUSD-M (Mezzanine)', '$MYCO (Junior)'],\n", + " colors=['#2ecc71', '#f39c12', '#e74c3c'], alpha=0.8)\n", + "ax.set_xlabel('Day')\n", + "ax.set_ylabel('Token Supply ($)')\n", + "ax.set_title('Tranche Supplies Over Time')\n", + "ax.legend(loc='upper left', fontsize=8)\n", + "ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'${x/1e6:.2f}M'))\n", + "\n", + "# --- Plot 2: Collateral ratios ---\n", + "ax = axes[1]\n", + "ax.plot(days, senior_cr, color='#2ecc71', label='Senior CR')\n", + "ax.plot(days, mez_cr, color='#f39c12', label='Mezzanine CR')\n", + "ax.plot(days, junior_cr, color='#e74c3c', label='Junior CR')\n", + "ax.plot(days, system_cr, color='#3498db', linestyle='--', label='System CR')\n", + "ax.axhline(1.5, color='#2ecc71', linestyle=':', alpha=0.5, label='Senior min CR')\n", + "ax.axhline(1.2, color='#f39c12', linestyle=':', alpha=0.5, label='Mezzanine min CR')\n", + "ax.set_xlabel('Day')\n", + "ax.set_ylabel('Collateral Ratio')\n", + "ax.set_title('Collateral Ratios by Tranche')\n", + "ax.legend(fontsize=7)\n", + "\n", + "# --- Plot 3: Cumulative yield waterfall ---\n", + "ax = axes[2]\n", + "ax.stackplot(days, senior_yield, mez_yield, junior_yield,\n", + " labels=['Senior yield', 'Mezzanine yield', 'Junior yield'],\n", + " colors=['#2ecc71', '#f39c12', '#e74c3c'], alpha=0.8)\n", + "ax.set_xlabel('Day')\n", + "ax.set_ylabel('Cumulative Yield ($)')\n", + "ax.set_title('Cumulative Yield Waterfall')\n", + "ax.legend(loc='upper left', fontsize=8)\n", + "ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'${x/1e3:.1f}K'))\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Stress Test — 40% Collateral Loss at Day 180\n", + "\n", + "At day 180 we simulate a sudden 40% drop in collateral value (e.g. correlated LST slashing or depeg).\n", + "The loss waterfall means Junior absorbs first, then Mezzanine, then Senior as a last resort." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "STRESS_DAY = 180\n", + "LOSS_FRACTION = 0.40\n", + "\n", + "stress_system = copy.deepcopy(system) # Start fresh from initial mint\n", + "stress_history = [snapshot(stress_system, 0)]\n", + "\n", + "for day in range(1, DAYS + 1):\n", + " # Apply daily staking yield\n", + " daily_yield = stress_system.total_collateral * STAKING_APY * DT\n", + " stress_system = distribute_yield(stress_system, daily_yield, DT)\n", + "\n", + " # Apply 40% loss at day 180\n", + " if day == STRESS_DAY:\n", + " loss = stress_system.total_collateral * LOSS_FRACTION\n", + " stress_system = apply_loss(stress_system, loss)\n", + " print(f\"Day {day}: Applied ${loss:,.0f} loss ({LOSS_FRACTION*100:.0f}% of collateral)\")\n", + " liq = check_liquidation(stress_system)\n", + " print(f\" Liquidation flags: {liq}\")\n", + " m = get_tranche_metrics(stress_system)\n", + " print(f\" Junior cumulative losses: ${m['junior']['losses']:,.0f}\")\n", + " print(f\" Mezzanine cumulative losses: ${m['mezzanine']['losses']:,.0f}\")\n", + " print(f\" Senior cumulative losses: ${m['senior']['losses']:,.0f}\")\n", + "\n", + " stress_history.append(snapshot(stress_system, day))\n", + "\n", + "s_days = [h[\"day\"] for h in stress_history]\n", + "s_senior_cr = [h[\"senior_cr\"] for h in stress_history]\n", + "s_mez_cr = [h[\"mez_cr\"] for h in stress_history]\n", + "s_junior_cr = [h[\"junior_cr\"] for h in stress_history]\n", + "s_system_cr = [h[\"system_cr\"] for h in stress_history]\n", + "s_total_col = [h[\"total_collateral\"] for h in stress_history]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(1, 2, figsize=(16, 6))\n", + "\n", + "# --- Plot 1: Collateral ratios under stress ---\n", + "ax = axes[0]\n", + "ax.plot(s_days, s_senior_cr, color='#2ecc71', linewidth=2, label='Senior CR')\n", + "ax.plot(s_days, s_mez_cr, color='#f39c12', linewidth=2, label='Mezzanine CR')\n", + "ax.plot(s_days, s_junior_cr, color='#e74c3c', linewidth=2, label='Junior CR')\n", + "ax.plot(s_days, s_system_cr, color='#3498db', linestyle='--', linewidth=1.5, label='System CR')\n", + "ax.axvline(STRESS_DAY, color='black', linestyle='--', alpha=0.7, label=f'Loss event (day {STRESS_DAY})')\n", + "ax.axhline(1.0, color='gray', linestyle=':', alpha=0.5, label='CR = 1.0 (par)')\n", + "ax.set_xlabel('Day')\n", + "ax.set_ylabel('Collateral Ratio')\n", + "ax.set_title(f'Collateral Ratios — 40% Loss at Day {STRESS_DAY}')\n", + "ax.legend(fontsize=8)\n", + "ax.set_xlim(0, DAYS)\n", + "\n", + "# --- Plot 2: Total collateral and loss absorption breakdown ---\n", + "ax = axes[1]\n", + "ax.fill_between(s_days, 0, s_total_col, color='#3498db', alpha=0.4, label='Total Collateral')\n", + "ax.plot(s_days, s_total_col, color='#3498db', linewidth=2)\n", + "\n", + "# Overlay the loss waterfall at the shock event\n", + "shock_day_idx = STRESS_DAY\n", + "m_before = get_tranche_metrics(copy.deepcopy(stress_system))\n", + "stress_snap = stress_history[STRESS_DAY]\n", + "\n", + "ax.axvline(STRESS_DAY, color='black', linestyle='--', alpha=0.7, label=f'Shock (day {STRESS_DAY})')\n", + "ax.set_xlabel('Day')\n", + "ax.set_ylabel('Total Collateral ($)')\n", + "ax.set_title('Total Collateral Value Under Stress')\n", + "ax.legend(fontsize=8)\n", + "ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'${x/1e6:.2f}M'))\n", + "ax.set_xlim(0, DAYS)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Loss Absorption Bar Chart\n", + "\n", + "Compare the cumulative losses absorbed by each tranche after the stress event." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "final_metrics = get_tranche_metrics(stress_system)\n", + "\n", + "tranches = ['Junior ($MYCO)', 'Mezzanine (myUSD-M)', 'Senior (myUSD-S)']\n", + "losses_abs = [\n", + " final_metrics['junior']['losses'],\n", + " final_metrics['mezzanine']['losses'],\n", + " final_metrics['senior']['losses'],\n", + "]\n", + "yields_acc = [\n", + " final_metrics['junior']['yield'],\n", + " final_metrics['mezzanine']['yield'],\n", + " final_metrics['senior']['yield'],\n", + "]\n", + "\n", + "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n", + "\n", + "colors = ['#e74c3c', '#f39c12', '#2ecc71']\n", + "\n", + "ax = axes[0]\n", + "bars = ax.bar(tranches, losses_abs, color=colors, alpha=0.85, edgecolor='black', linewidth=0.7)\n", + "for bar, val in zip(bars, losses_abs):\n", + " ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 500,\n", + " f'${val:,.0f}', ha='center', va='bottom', fontsize=9)\n", + "ax.set_ylabel('Cumulative Losses Absorbed ($)')\n", + "ax.set_title('Loss Absorption by Tranche (End of Simulation)')\n", + "ax.tick_params(axis='x', labelsize=9)\n", + "\n", + "ax = axes[1]\n", + "bars = ax.bar(tranches, yields_acc, color=colors, alpha=0.85, edgecolor='black', linewidth=0.7)\n", + "for bar, val in zip(bars, yields_acc):\n", + " ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 50,\n", + " f'${val:,.0f}', ha='center', va='bottom', fontsize=9)\n", + "ax.set_ylabel('Cumulative Yield Received ($)')\n", + "ax.set_title('Yield Distribution by Tranche (End of Simulation)')\n", + "ax.tick_params(axis='x', labelsize=9)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()\n", + "\n", + "print(\"\\nFinal system state:\")\n", + "print(f\" Total collateral: ${final_metrics['total_collateral']:>12,.0f}\")\n", + "print(f\" System CR: {final_metrics['system_cr']:.4f}\")\n", + "print(f\" Senior CR: {final_metrics['senior']['cr']:.4f} (healthy: {final_metrics['senior']['healthy']})\")\n", + "print(f\" Mezzanine CR: {final_metrics['mezzanine']['cr']:.4f} (healthy: {final_metrics['mezzanine']['healthy']})\")\n", + "print(f\" Junior CR: {final_metrics['junior']['cr']:.4f} (healthy: {final_metrics['junior']['healthy']})\")" + ] + } + ] +} diff --git a/notebooks/08_crosschain_simulation.ipynb b/notebooks/08_crosschain_simulation.ipynb new file mode 100644 index 0000000..59d9b5c --- /dev/null +++ b/notebooks/08_crosschain_simulation.ipynb @@ -0,0 +1,369 @@ +{ + "nbformat": 4, + "nbformat_minor": 4, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11.0" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Cross-Chain Hub-and-Spoke Simulation\n", + "\n", + "Explores the multi-chain collateral architecture of MycoFi:\n", + "\n", + "- **Hub (Base)**: Registry, bonding curve, tranche manager, treasury\n", + "- **Spokes**: Ethereum, Arbitrum, Optimism, Base, Polygon — each with a collateral vault\n", + "- **CCIP messages**: Deposit reports, state syncs, and rebalancing triggers flow between hub and spokes\n", + "\n", + "Each chain hosts different LST/LRT assets with their own APYs, prices, and risk scores." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.ticker as mticker\n", + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath('..'))\n", + "\n", + "np.random.seed(42)\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams['figure.figsize'] = (14, 5)\n", + "plt.rcParams['figure.dpi'] = 100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Create the Default 5-Chain System\n", + "\n", + "The default system includes chains: Ethereum, Arbitrum, Optimism, Base, and Polygon,\n", + "each pre-configured with typical LST assets and APYs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from src.crosschain.hub_spoke import (\n", + " create_default_system,\n", + " simulate_deposit,\n", + " tick,\n", + " apply_price_shock,\n", + " get_crosschain_metrics,\n", + ")\n", + "\n", + "system = create_default_system()\n", + "\n", + "chains = list(system.hub.spokes.keys())\n", + "print(f\"Chains registered: {chains}\")\n", + "print()\n", + "for chain, spoke in system.hub.spokes.items():\n", + " assets = [a.symbol for a in spoke.accepted_assets]\n", + " print(f\" {chain:12s}: {assets}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Seed Deposits Across All Chains\n", + "\n", + "Deposit a realistic initial allocation of LST collateral across all spoke chains." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Seed initial deposits per chain\n", + "seed_deposits = {\n", + " \"ethereum\": [(\"stETH\", 120.0), (\"rETH\", 60.0), (\"cbETH\", 30.0)],\n", + " \"arbitrum\": [(\"wstETH\", 90.0), (\"rETH\", 40.0)],\n", + " \"optimism\": [(\"wstETH\", 70.0), (\"sfrxETH\", 30.0)],\n", + " \"base\": [(\"cbETH\", 50.0), (\"USDC\", 120_000.0)],\n", + " \"polygon\": [(\"stMATIC\", 250_000.0), (\"USDC\", 80_000.0)],\n", + "}\n", + "\n", + "for chain, assets in seed_deposits.items():\n", + " for symbol, amount in assets:\n", + " simulate_deposit(system, chain, symbol, amount, timestamp=0.0)\n", + "\n", + "# Process all CCIP messages to update hub state\n", + "system.hub.process_messages(0.01)\n", + "\n", + "metrics = get_crosschain_metrics(system)\n", + "print(f\"Total collateral after seeding: ${metrics['total_collateral_usd']:>14,.0f}\")\n", + "print()\n", + "for chain, data in metrics['chains'].items():\n", + " print(f\" {chain:12s}: ${data['total_value_usd']:>12,.0f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. 90-Day Simulation with Random Deposits and Price Movements\n", + "\n", + "Each day: apply staking yield, random deposits, mild ETH price drift, and process CCIP messages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import copy\n", + "\n", + "DAYS = 90\n", + "DT = 1.0 / 365 # One day in years\n", + "\n", + "# Assets that can receive random deposits per chain\n", + "deposit_assets = {\n", + " \"ethereum\": (\"stETH\", 2400.0),\n", + " \"arbitrum\": (\"wstETH\", 2410.0),\n", + " \"optimism\": (\"wstETH\", 2410.0),\n", + " \"base\": (\"USDC\", 1.0),\n", + " \"polygon\": (\"USDC\", 1.0),\n", + "}\n", + "\n", + "# Track per-chain collateral over time\n", + "chain_history = {ch: [] for ch in chains}\n", + "total_history = []\n", + "msg_history = []\n", + "yield_history = []\n", + "\n", + "# ETH GBM parameters\n", + "eth_mu = 0.0\n", + "eth_sigma = 0.6 # 60% annualised volatility\n", + "eth_price = 2400.0\n", + "eth_assets = [\"stETH\", \"rETH\", \"cbETH\", \"wstETH\", \"sfrxETH\"]\n", + "\n", + "normal_system = copy.deepcopy(system) # preserve original for stress test\n", + "\n", + "for day in range(DAYS):\n", + " t = day * DT\n", + "\n", + " # Random deposits (Poisson arrivals, ~3 per day)\n", + " n_deposits = np.random.poisson(3)\n", + " for _ in range(n_deposits):\n", + " chain = np.random.choice(chains)\n", + " symbol, unit_price = deposit_assets[chain]\n", + " usd_amount = np.random.exponential(10_000) # avg $10k deposit\n", + " qty = usd_amount / max(unit_price, 1)\n", + " simulate_deposit(normal_system, chain, symbol, qty, timestamp=t)\n", + "\n", + " # ETH price GBM step\n", + " dW = np.random.normal(0, np.sqrt(DT))\n", + " eth_price *= np.exp((eth_mu - 0.5 * eth_sigma**2) * DT + eth_sigma * dW)\n", + " multiplier = eth_price / 2400.0\n", + " for asset_sym in eth_assets:\n", + " for spoke in normal_system.hub.spokes.values():\n", + " for asset in spoke.accepted_assets:\n", + " if asset.symbol == asset_sym:\n", + " # Reset to base * multiplier (avoid compounding the multiplier)\n", + " pass # Prices are tracked relatively — we only shock in stress test\n", + "\n", + " # Tick system (applies yield + processes messages)\n", + " tick_data = tick(normal_system, DT)\n", + "\n", + " # Record\n", + " for ch in chains:\n", + " chain_history[ch].append(normal_system.hub.spokes[ch].total_value_usd)\n", + " total_history.append(tick_data[\"total_collateral_usd\"])\n", + " msg_history.append(normal_system.total_messages_delivered)\n", + " yield_history.append(normal_system.total_yield_generated)\n", + "\n", + "print(f\"Simulation complete.\")\n", + "print(f\"Final total collateral: ${total_history[-1]:,.0f}\")\n", + "print(f\"Total CCIP messages delivered: {msg_history[-1]}\")\n", + "print(f\"Total yield generated: ${yield_history[-1]:,.0f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Visualizations — Normal Operation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "day_axis = np.arange(DAYS)\n", + "\n", + "chain_colors = {\n", + " \"ethereum\": '#627EEA',\n", + " \"arbitrum\": '#28A0F0',\n", + " \"optimism\": '#FF0420',\n", + " \"base\": '#0052FF',\n", + " \"polygon\": '#8247E5',\n", + "}\n", + "\n", + "fig, axes = plt.subplots(1, 3, figsize=(20, 6))\n", + "\n", + "# --- Plot 1: Stacked area — per-chain collateral ---\n", + "ax = axes[0]\n", + "stacked_data = [chain_history[ch] for ch in chains]\n", + "ax.stackplot(day_axis, stacked_data,\n", + " labels=chains,\n", + " colors=[chain_colors[c] for c in chains],\n", + " alpha=0.85)\n", + "ax.set_xlabel('Day')\n", + "ax.set_ylabel('Collateral Value (USD)')\n", + "ax.set_title('Per-Chain Collateral — Stacked Area')\n", + "ax.legend(loc='upper left', fontsize=8)\n", + "ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f'${x/1e6:.1f}M'))\n", + "\n", + "# --- Plot 2: Asset breakdown pie chart (final state) ---\n", + "ax = axes[1]\n", + "final_metrics = get_crosschain_metrics(normal_system)\n", + "asset_values = {}\n", + "for chain_data in final_metrics['chains'].values():\n", + " for sym, data in chain_data['assets'].items():\n", + " asset_values[sym] = asset_values.get(sym, 0.0) + data['value']\n", + "# Sort by value\n", + "sorted_assets = sorted(asset_values.items(), key=lambda x: x[1], reverse=True)\n", + "labels, vals = zip(*sorted_assets)\n", + "ax.pie(vals, labels=labels, autopct='%1.1f%%', startangle=90,\n", + " colors=plt.cm.Set3(np.linspace(0, 1, len(vals))))\n", + "ax.set_title('Asset Breakdown by Value (Day 90)')\n", + "\n", + "# --- Plot 3: CCIP message delivery timeline ---\n", + "ax = axes[2]\n", + "msg_per_day = np.diff([0] + msg_history)\n", + "ax.bar(day_axis, msg_per_day, color='#3498db', alpha=0.7, label='Messages delivered')\n", + "ax.plot(day_axis, np.cumsum(msg_per_day), color='#e74c3c', linewidth=2, label='Cumulative')\n", + "ax2 = ax.twinx()\n", + "ax2.plot(day_axis, yield_history, color='#2ecc71', linewidth=2, linestyle='--', label='Cumulative yield')\n", + "ax2.set_ylabel('Cumulative Yield ($)', color='#2ecc71')\n", + "ax2.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f'${x/1e3:.1f}K'))\n", + "ax.set_xlabel('Day')\n", + "ax.set_ylabel('CCIP Messages')\n", + "ax.set_title('CCIP Message Timeline & Yield')\n", + "lines1, labels1 = ax.get_legend_handles_labels()\n", + "lines2, labels2 = ax2.get_legend_handles_labels()\n", + "ax.legend(lines1 + lines2, labels1 + labels2, fontsize=8)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Stress Test — ETH Crash and Cross-Chain Impact Propagation\n", + "\n", + "Apply a sudden 50% ETH price crash at day 45 and observe how it propagates across all chains\n", + "that hold ETH-denominated assets (Ethereum, Arbitrum, Optimism) while stablecoin-heavy\n", + "chains (Base USDC, Polygon USDC) show relative stability." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CRASH_DAY = 45\n", + "CRASH_FACTOR = 0.50 # 50% price crash\n", + "\n", + "stress_system = copy.deepcopy(system) # Start from the seeded state\n", + "\n", + "stress_chain_history = {ch: [] for ch in chains}\n", + "stress_total_history = []\n", + "\n", + "for day in range(DAYS):\n", + " t = day * DT\n", + "\n", + " # Small random deposits\n", + " for chain, (symbol, unit_price) in deposit_assets.items():\n", + " if np.random.random() < 0.4:\n", + " qty = np.random.exponential(5_000) / max(unit_price, 1)\n", + " simulate_deposit(stress_system, chain, symbol, qty, timestamp=t)\n", + "\n", + " # ETH crash at day 45\n", + " if day == CRASH_DAY:\n", + " print(f\"Day {day}: ETH crashes by {(1-CRASH_FACTOR)*100:.0f}%\")\n", + " for eth_asset in [\"stETH\", \"rETH\", \"cbETH\", \"wstETH\", \"sfrxETH\"]:\n", + " apply_price_shock(stress_system, eth_asset, CRASH_FACTOR)\n", + " pre_crash = stress_system.hub.total_collateral_usd\n", + " stress_system.hub._recalculate_total()\n", + " post_crash = stress_system.hub.total_collateral_usd\n", + " print(f\" Pre-crash total collateral: ${pre_crash:,.0f}\")\n", + " print(f\" Post-crash total collateral: ${post_crash:,.0f}\")\n", + " print(f\" Drop: {(pre_crash - post_crash)/pre_crash*100:.1f}%\")\n", + "\n", + " tick(stress_system, DT)\n", + "\n", + " for ch in chains:\n", + " stress_chain_history[ch].append(stress_system.hub.spokes[ch].total_value_usd)\n", + " stress_total_history.append(stress_system.hub.total_collateral_usd)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(1, 2, figsize=(16, 6))\n", + "\n", + "# --- Plot 1: Per-chain collateral under stress ---\n", + "ax = axes[0]\n", + "for ch in chains:\n", + " ax.plot(day_axis, stress_chain_history[ch],\n", + " label=ch, color=chain_colors[ch], linewidth=2)\n", + "ax.axvline(CRASH_DAY, color='black', linestyle='--', linewidth=1.5, label=f'ETH crash (day {CRASH_DAY})')\n", + "ax.set_xlabel('Day')\n", + "ax.set_ylabel('Chain Collateral Value ($)')\n", + "ax.set_title('Per-Chain Collateral — ETH Crash Stress Test')\n", + "ax.legend(fontsize=8)\n", + "ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f'${x/1e6:.1f}M'))\n", + "\n", + "# --- Plot 2: Normal vs stress total collateral ---\n", + "ax = axes[1]\n", + "ax.plot(day_axis, total_history, color='#2ecc71', linewidth=2, label='Normal')\n", + "ax.plot(day_axis, stress_total_history, color='#e74c3c', linewidth=2, label='Stress (ETH crash)')\n", + "ax.axvline(CRASH_DAY, color='black', linestyle='--', linewidth=1.5, label=f'Shock (day {CRASH_DAY})')\n", + "ax.fill_between(day_axis, stress_total_history, total_history,\n", + " color='#e74c3c', alpha=0.15, label='Loss from shock')\n", + "ax.set_xlabel('Day')\n", + "ax.set_ylabel('Total Collateral ($)')\n", + "ax.set_title('System Collateral: Normal vs Stress')\n", + "ax.legend(fontsize=8)\n", + "ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f'${x/1e6:.1f}M'))\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ] +} diff --git a/notebooks/09_conviction_governance.ipynb b/notebooks/09_conviction_governance.ipynb new file mode 100644 index 0000000..aa95ca3 --- /dev/null +++ b/notebooks/09_conviction_governance.ipynb @@ -0,0 +1,375 @@ +{ + "nbformat": 4, + "nbformat_minor": 4, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11.0" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Conviction Voting Governance\n", + "\n", + "Explores the continuous conviction voting mechanism used to govern MycoFi parameters.\n", + "\n", + "**Core formula:** $y_{t+1} = \\alpha \\cdot y_t + x_t$\n", + "\n", + "**Trigger function:** $\\tau(r) = \\frac{\\rho S}{(1-\\alpha)(\\beta - r/R)^2}$\n", + "\n", + "Where:\n", + "- $\\alpha$: decay factor (determines how quickly conviction charges/discharges)\n", + "- $\\rho$: scale parameter controlling how much conviction is needed overall\n", + "- $\\beta$: maximum share of funds any single proposal can request\n", + "- $r$: funds requested, $R$: total funds, $S$: token supply" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.colors as mcolors\n", + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath('..'))\n", + "\n", + "np.random.seed(7)\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams['figure.figsize'] = (14, 5)\n", + "plt.rcParams['figure.dpi'] = 100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Conviction Charging and Discharging Curves\n", + "\n", + "Compare four different half-life settings (3, 7, 14, 30 epochs) to visualise how quickly\n", + "conviction builds up when tokens are staked and decays when tokens are withdrawn." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from src.primitives.conviction import (\n", + " ConvictionParams,\n", + " Proposal,\n", + " Voter,\n", + " ConvictionSystem,\n", + " trigger_threshold,\n", + " update_conviction,\n", + " max_conviction,\n", + " conviction_at_time,\n", + " epochs_to_fraction,\n", + " generate_conviction_curves,\n", + " stake,\n", + " unstake,\n", + " tick,\n", + " get_governance_metrics,\n", + ")\n", + "\n", + "HALF_LIVES = [3, 7, 14, 30]\n", + "STAKED_TOKENS = 1000.0\n", + "EPOCHS = 120\n", + "\n", + "fig, axes = plt.subplots(1, 2, figsize=(16, 6))\n", + "\n", + "colors = ['#e74c3c', '#f39c12', '#2ecc71', '#3498db']\n", + "\n", + "for hl, color in zip(HALF_LIVES, colors):\n", + " p = ConvictionParams.from_half_life(hl)\n", + " curves = generate_conviction_curves(STAKED_TOKENS, p.alpha, EPOCHS)\n", + "\n", + " # Charging curve\n", + " axes[0].plot(curves['time'], curves['charge'] / curves['max'],\n", + " color=color, linewidth=2, label=f'Half-life = {hl} epochs (α={p.alpha:.4f})')\n", + "\n", + " # Discharging curve\n", + " axes[1].plot(curves['time'], curves['discharge'] / curves['max'],\n", + " color=color, linewidth=2, label=f'Half-life = {hl} epochs')\n", + "\n", + "# Reference lines\n", + "for ax, title, ylabel in [\n", + " (axes[0], 'Conviction Charging (Stake held continuously)', 'Conviction / Max Conviction'),\n", + " (axes[1], 'Conviction Discharging (Stake removed at t=0)', 'Conviction / Max Conviction'),\n", + "]:\n", + " ax.axhline(0.5, color='gray', linestyle=':', alpha=0.6, label='50% of max')\n", + " ax.axhline(0.9, color='gray', linestyle='--', alpha=0.4, label='90% of max')\n", + " ax.set_xlabel('Epochs')\n", + " ax.set_ylabel(ylabel)\n", + " ax.set_title(title)\n", + " ax.legend(fontsize=8)\n", + " ax.set_ylim(-0.05, 1.1)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()\n", + "\n", + "print(\"Epochs to reach 90% of max conviction:\")\n", + "for hl in HALF_LIVES:\n", + " p = ConvictionParams.from_half_life(hl)\n", + " e = epochs_to_fraction(0.9, p.alpha)\n", + " print(f\" Half-life {hl:2d} epochs: {e:.1f} epochs to 90%\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Full Governance Simulation\n", + "\n", + "Create a conviction system with 20 voters, 4 proposals requesting different fund shares,\n", + "and run 100 epochs. Voters stake tokens semi-randomly based on their sentiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Build the governance system\n", + "params = ConvictionParams(\n", + " alpha=0.9,\n", + " beta=0.2,\n", + " rho=0.0025,\n", + " min_age=3,\n", + ")\n", + "\n", + "gov = ConvictionSystem(\n", + " params=params,\n", + " total_supply=100_000.0,\n", + " total_funds=50_000.0,\n", + ")\n", + "\n", + "# Add 20 voters with log-normally distributed holdings\n", + "N_VOTERS = 20\n", + "for i in range(N_VOTERS):\n", + " vid = f\"voter_{i}\"\n", + " holdings = float(np.random.lognormal(mean=np.log(5000), sigma=1.0))\n", + " gov.voters[vid] = Voter(\n", + " id=vid,\n", + " holdings=holdings,\n", + " sentiment=float(np.random.uniform(0.3, 0.9)),\n", + " )\n", + "\n", + "gov.total_supply = sum(v.holdings for v in gov.voters.values())\n", + "print(f\"Total token supply: {gov.total_supply:,.0f}\")\n", + "\n", + "# Add 4 proposals with different fund requests\n", + "proposals_config = [\n", + " (\"P-001\", \"Raise senior CR to 1.6\", 0.05, \"parameter_change\"),\n", + " (\"P-002\", \"Add wBTC as collateral\", 0.12, \"chain_onboard\"),\n", + " (\"P-003\", \"Adjust mezzanine yield target 10%\",0.08, \"parameter_change\"),\n", + " (\"P-004\", \"Emergency reserve allocation\", 0.18, \"weight_update\"),\n", + "]\n", + "\n", + "for pid, title, share, ptype in proposals_config:\n", + " gov.proposals[pid] = Proposal(\n", + " id=pid,\n", + " title=title,\n", + " funds_requested=share,\n", + " proposal_type=ptype,\n", + " status=\"candidate\",\n", + " )\n", + "\n", + "# Initial staking: each voter distributes a fraction of their holdings across proposals\n", + "prop_ids = list(gov.proposals.keys())\n", + "for voter in gov.voters.values():\n", + " available = voter.holdings * np.random.uniform(0.3, 0.8) # stake 30-80% of holdings\n", + " n_props = np.random.randint(1, len(prop_ids) + 1)\n", + " chosen = np.random.choice(prop_ids, size=n_props, replace=False)\n", + " weights = np.random.dirichlet(np.ones(n_props))\n", + " for pid, w in zip(chosen, weights):\n", + " amount = available * w * voter.sentiment\n", + " stake(gov, voter.id, pid, amount)\n", + "\n", + "print(f\"Proposals created: {list(gov.proposals.keys())}\")\n", + "for pid, prop in gov.proposals.items():\n", + " thr = trigger_threshold(prop.funds_requested, gov.total_supply, params)\n", + " print(f\" {pid}: '{prop.title[:30]}...' share={prop.funds_requested:.0%} trigger={thr:,.0f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Run 100 epochs\n", + "N_EPOCHS = 100\n", + "\n", + "conviction_history = {pid: [] for pid in prop_ids}\n", + "trigger_history = {pid: [] for pid in prop_ids}\n", + "progress_history = {pid: [] for pid in prop_ids}\n", + "\n", + "for epoch in range(N_EPOCHS):\n", + " gov = tick(gov)\n", + "\n", + " # Every 10 epochs some voters adjust stakes\n", + " if epoch % 10 == 0 and epoch > 0:\n", + " for voter in list(gov.voters.values())[:10]:\n", + " active_props = [p for p in gov.proposals.values() if p.status == \"candidate\"]\n", + " if active_props:\n", + " pid = np.random.choice([p.id for p in active_props])\n", + " new_amount = voter.holdings * 0.05 * voter.sentiment\n", + " stake(gov, voter.id, pid, new_amount)\n", + "\n", + " metrics = get_governance_metrics(gov)\n", + " for pid in prop_ids:\n", + " pdata = metrics['proposals'].get(pid, {})\n", + " conviction_history[pid].append(pdata.get('conviction', 0.0))\n", + " trigger_history[pid].append(pdata.get('trigger', float('inf')))\n", + " progress_history[pid].append(min(pdata.get('progress', 0.0), 1.5)) # cap for display\n", + "\n", + "print(f\"Simulation complete after {N_EPOCHS} epochs.\")\n", + "print(f\"Passed proposals: {[p.title for p in gov.passed_proposals]}\")\n", + "print(f\"Staking ratio: {metrics['staking_ratio']:.2%}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Conviction Progress Toward Trigger Per Proposal" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "epoch_axis = np.arange(1, N_EPOCHS + 1)\n", + "prop_colors = ['#e74c3c', '#f39c12', '#2ecc71', '#9b59b6']\n", + "\n", + "fig, axes = plt.subplots(1, 2, figsize=(18, 6))\n", + "\n", + "# --- Plot 1: Absolute conviction vs trigger ---\n", + "ax = axes[0]\n", + "for (pid, title, share, _), color in zip(proposals_config, prop_colors):\n", + " conv = conviction_history[pid]\n", + " trigs = trigger_history[pid]\n", + " ax.plot(epoch_axis, conv, color=color, linewidth=2, label=f'{pid}: {title[:25]}')\n", + " # Draw the trigger as a dashed line (first finite value)\n", + " finite_trigs = [t for t in trigs if t != float('inf')]\n", + " if finite_trigs:\n", + " ax.axhline(finite_trigs[0], color=color, linestyle='--', alpha=0.5)\n", + "\n", + "ax.set_xlabel('Epoch')\n", + "ax.set_ylabel('Total Conviction')\n", + "ax.set_title('Conviction Accumulation by Proposal')\n", + "ax.legend(fontsize=7)\n", + "\n", + "# --- Plot 2: Progress ratio (conviction / trigger) ---\n", + "ax = axes[1]\n", + "for (pid, title, share, _), color in zip(proposals_config, prop_colors):\n", + " progress = progress_history[pid]\n", + " ax.plot(epoch_axis, progress, color=color, linewidth=2, label=f'{pid}: {title[:25]}')\n", + "\n", + "ax.axhline(1.0, color='black', linestyle='--', linewidth=1.5, label='Trigger threshold (1.0)')\n", + "ax.fill_between(epoch_axis, 0, 1, alpha=0.05, color='gray', label='Below trigger')\n", + "ax.fill_between(epoch_axis, 1, 1.5, alpha=0.08, color='green', label='Passed')\n", + "ax.set_xlabel('Epoch')\n", + "ax.set_ylabel('Conviction / Trigger Threshold')\n", + "ax.set_title('Conviction Progress Toward Trigger')\n", + "ax.set_ylim(0, 1.55)\n", + "ax.legend(fontsize=7)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Trigger Function Contour Map\n", + "\n", + "Visualise how the conviction trigger threshold varies as a function of:\n", + "- **x-axis**: requested share of funds ($r/R$)\n", + "- **y-axis**: token supply ($S$)\n", + "\n", + "Darker regions require more conviction. The asymptote at $r/R = \\beta$ means proposals\n", + "requesting too large a share can never pass." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "shares = np.linspace(0.001, 0.185, 200)\n", + "supplies = np.linspace(10_000, 200_000, 200)\n", + "SS, RR = np.meshgrid(supplies, shares)\n", + "\n", + "# Compute trigger for each grid point\n", + "Z = np.zeros_like(SS)\n", + "for i in range(len(shares)):\n", + " for j in range(len(supplies)):\n", + " Z[i, j] = trigger_threshold(shares[i], supplies[j], params)\n", + "\n", + "# Cap extreme values for display\n", + "Z_display = np.clip(Z, 0, 5e6)\n", + "\n", + "fig, axes = plt.subplots(1, 2, figsize=(16, 6))\n", + "\n", + "# --- Contour map ---\n", + "ax = axes[0]\n", + "cf = ax.contourf(SS, RR, Z_display, levels=20, cmap='RdYlGn_r')\n", + "cs = ax.contour(SS, RR, Z_display, levels=10, colors='black', alpha=0.3, linewidths=0.5)\n", + "plt.colorbar(cf, ax=ax, label='Trigger Threshold (conviction units)')\n", + "ax.set_xlabel('Token Supply (S)')\n", + "ax.set_ylabel('Requested Share of Funds (r/R)')\n", + "ax.set_title('Conviction Trigger Contour Map')\n", + "ax.axhline(params.beta, color='red', linestyle='--', linewidth=2, label=f'β = {params.beta} (max share)')\n", + "ax.legend()\n", + "ax.xaxis.set_major_formatter(mticker := plt.FuncFormatter(lambda x, _: f'{x/1e3:.0f}K'))\n", + "ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x:.0%}'))\n", + "\n", + "# --- Fixed supply trigger curve ---\n", + "ax = axes[1]\n", + "fixed_supply = 100_000.0\n", + "safe_shares = shares[shares < params.beta * 0.95]\n", + "triggers = [trigger_threshold(s, fixed_supply, params) for s in safe_shares]\n", + "\n", + "ax.plot(safe_shares, triggers, color='#3498db', linewidth=2.5)\n", + "ax.set_xlabel('Requested Share of Funds (r/R)')\n", + "ax.set_ylabel('Required Conviction to Pass')\n", + "ax.set_title(f'Trigger Curve at Fixed Supply = {fixed_supply:,.0f} tokens')\n", + "ax.axvline(params.beta, color='red', linestyle='--', label=f'β = {params.beta}')\n", + "\n", + "# Annotate the 4 proposals\n", + "for (pid, title, share, _), color in zip(proposals_config, prop_colors):\n", + " if share < params.beta * 0.95:\n", + " thr = trigger_threshold(share, fixed_supply, params)\n", + " ax.scatter([share], [thr], color=color, s=100, zorder=5, label=f'{pid} ({share:.0%})')\n", + "\n", + "ax.set_ylim(0, min(max(triggers) * 1.1, 2e6))\n", + "ax.legend(fontsize=8)\n", + "ax.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x:.0%}'))\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ] +} diff --git a/notebooks/10_cadcad_monte_carlo.ipynb b/notebooks/10_cadcad_monte_carlo.ipynb new file mode 100644 index 0000000..f9db2fb --- /dev/null +++ b/notebooks/10_cadcad_monte_carlo.ipynb @@ -0,0 +1,360 @@ +{ + "nbformat": 4, + "nbformat_minor": 4, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11.0" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# cadCAD Full System Monte Carlo\n", + "\n", + "Runs the full MycoFi system through cadCAD with Monte Carlo sampling to characterise\n", + "the distribution of outcomes under normal growth and stress scenarios.\n", + "\n", + "**Scenarios:**\n", + "- `scenario_normal_growth`: Steady deposits, mild ETH volatility, governance ticking — 3 MC runs over 180 days\n", + "- `scenario_stress_test`: ETH crash at day 30, bank run follows — 5 MC runs over 100 days\n", + "\n", + "**Outputs:**\n", + "- Fan charts (mean ± 1σ) for supply, collateral, and system CR\n", + "- Tranche CR comparison under stress vs normal\n", + "- Summary statistics table" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.ticker as mticker\n", + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath('..'))\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams['figure.figsize'] = (14, 5)\n", + "plt.rcParams['figure.dpi'] = 100\n", + "pd.set_option('display.float_format', '{:,.2f}'.format)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Run Normal Growth Scenario\n", + "\n", + "3 Monte Carlo runs, 180 timesteps (one per day). Each run uses the same system configuration\n", + "but different random seeds for deposit arrivals and price movements." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from src.cadcad.config import scenario_normal_growth, scenario_stress_test\n", + "\n", + "print(\"Running normal growth scenario (3 runs × 180 days)...\")\n", + "df_normal = scenario_normal_growth(timesteps=180, runs=3)\n", + "print(f\"Done. DataFrame shape: {df_normal.shape}\")\n", + "print(f\"Columns: {list(df_normal.columns)}\")\n", + "df_normal.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Fan Charts — Normal Growth\n", + "\n", + "Plot supply, total collateral, and system collateral ratio across all Monte Carlo runs.\n", + "Shade the 1σ band around the mean to show the spread of outcomes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def fan_chart(ax, df, col, runs, color, label, formatter=None):\n", + " \"\"\"Plot a fan chart: individual run traces + mean ± 1σ band.\"\"\"\n", + " # Get all timesteps\n", + " timesteps = sorted(df['timestep'].unique())\n", + "\n", + " # Collect values per timestep across runs\n", + " per_ts = []\n", + " for ts in timesteps:\n", + " # Take the last substep only (final state per timestep)\n", + " sub = df[df['timestep'] == ts]\n", + " if 'substep' in sub.columns:\n", + " sub = sub[sub['substep'] == sub['substep'].max()]\n", + " vals = sub[col].values\n", + " per_ts.append(vals)\n", + "\n", + " per_ts = np.array(per_ts, dtype=float) # shape: (timesteps, runs)\n", + " mean = np.nanmean(per_ts, axis=1)\n", + " std = np.nanstd(per_ts, axis=1)\n", + " t = np.array(timesteps, dtype=float)\n", + "\n", + " # Individual run traces (thin)\n", + " for r in range(per_ts.shape[1]):\n", + " ax.plot(t, per_ts[:, r], color=color, linewidth=0.8, alpha=0.4)\n", + "\n", + " # Mean line\n", + " ax.plot(t, mean, color=color, linewidth=2.5, label=f'{label} (mean)')\n", + "\n", + " # ±1σ band\n", + " ax.fill_between(t, mean - std, mean + std, color=color, alpha=0.15, label=f'{label} ±1σ')\n", + "\n", + " if formatter:\n", + " ax.yaxis.set_major_formatter(formatter)\n", + "\n", + " return mean, std\n", + "\n", + "\n", + "fig, axes = plt.subplots(1, 3, figsize=(20, 6))\n", + "\n", + "runs = sorted(df_normal['run'].unique()) if 'run' in df_normal.columns else [0]\n", + "\n", + "# --- Total supply ---\n", + "fan_chart(axes[0], df_normal, 'total_supply', runs, '#3498db', 'Total Supply',\n", + " mticker.FuncFormatter(lambda x, _: f'${x/1e3:.0f}K'))\n", + "axes[0].set_xlabel('Timestep (day)')\n", + "axes[0].set_ylabel('Total Supply ($)')\n", + "axes[0].set_title('Total Token Supply — Normal Growth')\n", + "axes[0].legend(fontsize=8)\n", + "\n", + "# --- Total collateral ---\n", + "fan_chart(axes[1], df_normal, 'total_collateral_usd', runs, '#2ecc71', 'Total Collateral',\n", + " mticker.FuncFormatter(lambda x, _: f'${x/1e6:.2f}M'))\n", + "axes[1].set_xlabel('Timestep (day)')\n", + "axes[1].set_ylabel('Total Collateral (USD)')\n", + "axes[1].set_title('Total Collateral — Normal Growth')\n", + "axes[1].legend(fontsize=8)\n", + "\n", + "# --- System CR ---\n", + "fan_chart(axes[2], df_normal, 'system_cr', runs, '#e74c3c', 'System CR')\n", + "axes[2].axhline(1.0, color='black', linestyle='--', alpha=0.5, label='CR = 1.0 (par)')\n", + "axes[2].set_xlabel('Timestep (day)')\n", + "axes[2].set_ylabel('Collateral Ratio')\n", + "axes[2].set_title('System Collateral Ratio — Normal Growth')\n", + "axes[2].legend(fontsize=8)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Run Stress Test Scenario\n", + "\n", + "5 Monte Carlo runs, 100 timesteps. ETH crashes at day 30, bank run starts at day 31." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Running stress test scenario (5 runs × 100 days)...\")\n", + "df_stress = scenario_stress_test(timesteps=100, runs=5)\n", + "print(f\"Done. DataFrame shape: {df_stress.shape}\")\n", + "df_stress.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Tranche CRs Under Stress vs Normal\n", + "\n", + "Compare the collateral ratios for each tranche under both scenarios.\n", + "Senior should remain most protected; Junior absorbs the most volatility." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(1, 3, figsize=(20, 6))\n", + "\n", + "tranche_cols = [\n", + " ('senior_cr', '#2ecc71', 'Senior (myUSD-S)'),\n", + " ('mezzanine_cr', '#f39c12', 'Mezzanine (myUSD-M)'),\n", + " ('junior_cr', '#e74c3c', 'Junior ($MYCO)'),\n", + "]\n", + "\n", + "# Use only the last 100 timesteps of the normal run for comparison\n", + "df_normal_sub = df_normal[df_normal['timestep'] <= 100].copy()\n", + "runs_stress = sorted(df_stress['run'].unique()) if 'run' in df_stress.columns else [0]\n", + "runs_normal = sorted(df_normal_sub['run'].unique()) if 'run' in df_normal_sub.columns else [0]\n", + "\n", + "for ax, (col, color, title) in zip(axes, tranche_cols):\n", + " # Stress scenario fan\n", + " fan_chart(ax, df_stress, col, runs_stress, '#e74c3c', 'Stress')\n", + " # Normal scenario fan (overlaid)\n", + " fan_chart(ax, df_normal_sub, col, runs_normal, '#2ecc71', 'Normal')\n", + "\n", + " ax.axhline(1.0, color='black', linestyle=':', alpha=0.6, label='CR = 1.0')\n", + " ax.axvline(30, color='gray', linestyle='--', alpha=0.5, label='Shock day 30')\n", + " ax.set_xlabel('Timestep (day)')\n", + " ax.set_ylabel('Collateral Ratio')\n", + " ax.set_title(f'{title} CR')\n", + " ax.legend(fontsize=7)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Summary Statistics Table\n", + "\n", + "Aggregate key metrics at the final timestep for both scenarios, summarised across all MC runs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "metrics_of_interest = [\n", + " 'total_supply', 'total_collateral_usd', 'system_cr',\n", + " 'senior_cr', 'mezzanine_cr', 'junior_cr',\n", + " 'total_yield', 'ccip_messages', 'proposals_passed',\n", + "]\n", + "\n", + "def final_timestep_stats(df, label):\n", + " \"\"\"Extract final-timestep rows and compute summary statistics.\"\"\"\n", + " max_ts = df['timestep'].max()\n", + " final = df[df['timestep'] == max_ts]\n", + " if 'substep' in final.columns:\n", + " final = final[final['substep'] == final['substep'].max()]\n", + "\n", + " cols = [c for c in metrics_of_interest if c in final.columns]\n", + " stats = final[cols].agg(['mean', 'std', 'min', 'max']).T\n", + " stats.columns = [f'{label}_mean', f'{label}_std', f'{label}_min', f'{label}_max']\n", + " return stats\n", + "\n", + "normal_stats = final_timestep_stats(df_normal, 'normal')\n", + "stress_stats = final_timestep_stats(df_stress, 'stress')\n", + "\n", + "summary = pd.concat([normal_stats, stress_stats], axis=1)\n", + "print(\"=\" * 80)\n", + "print(\"Final Timestep Summary Statistics\")\n", + "print(\"=\" * 80)\n", + "print(summary.to_string())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Display as a formatted DataFrame\n", + "display_cols = [\n", + " 'normal_mean', 'normal_std',\n", + " 'stress_mean', 'stress_std',\n", + "]\n", + "display_rows = [\n", + " 'total_supply', 'total_collateral_usd', 'system_cr',\n", + " 'senior_cr', 'mezzanine_cr', 'junior_cr',\n", + " 'total_yield',\n", + "]\n", + "\n", + "display_df = summary[[c for c in display_cols if c in summary.columns]]\n", + "display_df = display_df.loc[[r for r in display_rows if r in display_df.index]]\n", + "\n", + "rename_map = {\n", + " 'total_supply': 'Total Token Supply ($)',\n", + " 'total_collateral_usd': 'Total Collateral (USD)',\n", + " 'system_cr': 'System Collateral Ratio',\n", + " 'senior_cr': 'Senior CR (myUSD-S)',\n", + " 'mezzanine_cr': 'Mezzanine CR (myUSD-M)',\n", + " 'junior_cr': 'Junior CR ($MYCO)',\n", + " 'total_yield': 'Cumulative Yield (USD)',\n", + "}\n", + "display_df.index = [rename_map.get(i, i) for i in display_df.index]\n", + "\n", + "col_rename = {\n", + " 'normal_mean': 'Normal (mean)',\n", + " 'normal_std': 'Normal (±1σ)',\n", + " 'stress_mean': 'Stress (mean)',\n", + " 'stress_std': 'Stress (±1σ)',\n", + "}\n", + "display_df = display_df.rename(columns=col_rename)\n", + "\n", + "display_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Normal vs Stress Supply and Collateral Comparison" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(1, 2, figsize=(16, 6))\n", + "\n", + "# --- Total collateral: normal vs stress ---\n", + "ax = axes[0]\n", + "fan_chart(ax, df_normal_sub, 'total_collateral_usd', runs_normal, '#2ecc71', 'Normal',\n", + " mticker.FuncFormatter(lambda x, _: f'${x/1e6:.2f}M'))\n", + "fan_chart(ax, df_stress, 'total_collateral_usd', runs_stress, '#e74c3c', 'Stress',\n", + " mticker.FuncFormatter(lambda x, _: f'${x/1e6:.2f}M'))\n", + "ax.axvline(30, color='gray', linestyle='--', alpha=0.6, label='Shock day 30')\n", + "ax.set_xlabel('Timestep (day)')\n", + "ax.set_ylabel('Total Collateral (USD)')\n", + "ax.set_title('Total Collateral: Normal vs Stress')\n", + "ax.legend(fontsize=8)\n", + "\n", + "# --- System CR: normal vs stress ---\n", + "ax = axes[1]\n", + "fan_chart(ax, df_normal_sub, 'system_cr', runs_normal, '#2ecc71', 'Normal')\n", + "fan_chart(ax, df_stress, 'system_cr', runs_stress, '#e74c3c', 'Stress')\n", + "ax.axhline(1.0, color='black', linestyle=':', alpha=0.6, label='CR = 1.0 (par)')\n", + "ax.axvline(30, color='gray', linestyle='--', alpha=0.6, label='Shock day 30')\n", + "ax.set_xlabel('Timestep (day)')\n", + "ax.set_ylabel('System Collateral Ratio')\n", + "ax.set_title('System CR: Normal vs Stress')\n", + "ax.legend(fontsize=8)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ] +} diff --git a/param_sweep_results.csv b/param_sweep_results.csv new file mode 100644 index 0000000..b99c61e --- /dev/null +++ b/param_sweep_results.csv @@ -0,0 +1,406 @@ +senior_cr_param,mez_cr_param,volatility,staking_apy,sim,final_total_collateral,final_senior_cr,final_mez_cr,final_junior_cr,min_senior_cr,min_mez_cr,min_junior_cr,senior_underwater_days,mez_underwater_days,senior_yield,mez_yield,junior_yield,senior_losses,mez_losses,junior_losses +1.2,1.05,0.3,0.03,0,723182.6900686854,-4.644876696664182,0.0002658294061013388,0.0,-4.645041080225827,0.0,0.0,284,343,9999.999999999938,17892.24413354125,1127.8327542933855,1958292.2322213955,267828.95141780266,151127.83275429328 +1.2,1.05,0.3,0.03,1,881528.6837454274,-4.487129592578308,0.0,0.0,-4.487129592578308,0.0,0.0,288,336,9999.999999999938,18533.201448869,1517.0529393977183,1905709.8641927724,268533.20144886867,151517.05293939766 +1.2,1.05,0.3,0.03,2,1197560.4017689829,-3.5492973146157487,0.0,0.0,-3.5492973146157487,0.0,0.0,301,345,9999.999999999938,16663.67698670151,1306.2886001233492,1593099.1048719143,266663.67698670126,151306.28860012346 +1.2,1.05,0.3,0.03,3,1011870.4749462897,-4.131420308256357,0.00021917808219178083,2.2373037944940346e-05,-4.131502500037179,0.0,0.0,300,335,9999.999999999938,17544.651595014842,458.47492346820184,1787140.102752119,267492.4663373499,150455.11896777648 +1.2,1.05,0.3,0.03,4,753060.5708634733,-5.207841691429542,0.0,0.0,-5.207841691429542,0.0,0.0,261,343,9999.999999999938,18280.710211421756,2783.2543562491473,2145947.2304765177,268280.71021142136,152783.25435624926 +1.2,1.05,0.3,0.04,0,873409.9022309731,-4.546699519533178,0.0,0.0,-4.546699519533178,0.0,0.0,300,340,9999.999999999938,19047.619047618962,9107.006567614513,1925566.5065110605,269047.61904761824,159107.00656761418 +1.2,1.05,0.3,0.04,1,1052690.2781356417,-4.543566339623543,0.00021917808219178083,0.00023772734226514127,-4.543648531404365,0.0,0.0,283,329,9999.999999999938,19047.619047618962,12089.284412913614,1924522.1132078494,268995.4337899535,162053.6253115736 +1.2,1.05,0.3,0.04,2,647611.3086592472,-4.110808061623627,0.000181118591201229,0.0,-4.110890253404449,0.0,0.0,293,346,9999.999999999938,17652.8251016562,6028.776055647605,1780269.353874545,267609.7016275599,156028.7760556476 +1.2,1.05,0.3,0.04,3,1180312.6707709914,-5.030049850148938,0.0,0.0,-5.030049850148938,0.0,0.0,304,342,9999.999999999938,19047.619047618962,13573.142913915108,2086683.2833829825,269047.61904761824,163573.1429139152 +1.2,1.05,0.3,0.04,4,1051992.929282678,-5.153019823328774,0.0,0.0,-5.153019823328774,0.0,0.0,269,340,9999.999999999938,19047.619047618962,15330.04872403177,2127673.274442927,269047.6190476181,165330.04872403186 +1.2,1.05,0.3,0.05,0,1438387.9396131255,-5.866504775853553,0.00021917808219178083,0.0007504784080283012,-5.866586967634375,0.0,0.0,274,330,9999.999999999938,19047.619047618962,34163.83055340759,2365501.59195119,268995.4337899535,184051.25879220324 +1.2,1.05,0.3,0.05,1,1552298.984887595,-5.269656907621424,0.00021917808219178083,0.0008566272206803503,-5.269739099402246,0.0,0.0,272,331,9999.999999999938,19047.619047618962,35207.82281084244,2166552.302540479,268995.4337899535,185079.3287277404 +1.2,1.05,0.3,0.05,2,2133035.087517044,-5.861669003417668,0.00043835616438356166,0.0027785483618871613,-5.861833386979312,0.0,0.0,281,335,9999.999999999938,19047.619047618962,46417.77940040327,2363889.667805895,268943.2485322888,196000.99714612015 +1.2,1.05,0.3,0.05,3,606031.4642085825,-3.7670672169781345,0.0,0.0,-3.7670672169781345,0.0,0.0,292,342,9999.999999999938,19047.619047618962,12984.69749868973,1665689.0723260443,269047.6190476182,162984.6974986897 +1.2,1.05,0.3,0.05,4,1343267.2695905517,-4.640690576686028,0.0,0.0,-4.640690576686028,0.0,0.0,300,339,9999.999999999938,19047.619047618962,26439.625111341444,1956896.8588953423,269047.6190476182,176439.62511134148 +1.2,1.05,0.6,0.03,0,296667.0126861121,-9.090779460734188,0.0,0.0,-9.090991482279437,0.0,0.0,321,357,9963.707141732702,11684.376929156599,252.12108650174673,3440223.527386446,261684.37692915637,150252.12108650184 +1.2,1.05,0.6,0.03,1,1373945.927691695,-13.780947830898315,0.00021917808219178083,0.0001936395548804933,-13.781030022679136,0.0,0.0,326,349,9999.999999999938,19005.42869240705,8386.190549081226,5003649.276966085,268953.24343474157,158357.14461584913 +1.2,1.05,0.6,0.03,2,475084.8635985612,-6.398102719723502,0.0,0.0,-6.398102719723502,0.0,0.0,338,350,9999.999999999938,6957.2971536215155,269.096747501541,2542700.906574509,256957.29715362148,150269.09674750155 +1.2,1.05,0.6,0.03,3,1111708.1729275226,-7.861811230357822,0.00021917808219178083,5.443552888706672e-05,-7.861893422138643,0.0,0.0,332,357,9999.999999999938,12122.311906346915,122.67592841588356,3030603.743452606,262070.12664868202,150114.51059908274 +1.2,1.05,0.6,0.03,4,278079.0588561935,-5.30325270382507,0.0,0.0,-5.30325270382507,0.0,0.0,332,359,9802.206252542357,5492.536785422308,4.045100248053217,2177553.107527566,255492.53678542227,150004.04510024804 +1.2,1.05,0.6,0.04,0,316339.74474617233,-6.867901962987887,8.040595638834874e-05,0.0,-6.868148538330351,0.0,0.0,330,349,9999.999999999938,10541.69684828549,1875.8366688474637,2699300.654329298,260522.55257295465,151875.83666884745 +1.2,1.05,0.6,0.04,1,599572.4167968526,-6.657100624322192,0.0002991960773800983,0.0,-6.657265007883835,0.0,0.0,341,353,9999.999999999938,13788.383134777958,1945.8576189177231,2629033.5414407374,263717.1459734967,151945.85761891774 +1.2,1.05,0.6,0.04,2,1502498.5729199103,-20.215914778959217,0.0,0.0,-20.215914778959217,0.0,0.0,317,340,9999.999999999938,19047.619047618962,41028.53382763758,7148638.259653048,269047.6190476183,191028.53382763744 +1.2,1.05,0.6,0.04,3,588459.109510794,-8.309451925502824,0.00029202726753259307,0.0,-8.309616309064467,0.0,0.0,339,360,9999.999999999938,15614.65841973184,2611.1319158715423,3179817.3085009307,265545.12811793806,152611.13191587155 +1.2,1.05,0.6,0.04,4,658683.5002986803,-11.272394838366635,0.0,0.0,-11.272394838366635,0.0,0.0,327,354,9999.999999999938,19029.196561599187,10591.834503897695,4167464.9461221946,269029.19656159845,160591.83450389787 +1.2,1.05,0.6,0.05,0,779612.4302426161,-10.346169448752171,0.00043835616438356166,0.00034411101205758456,-10.346333832313814,0.0,0.0,327,360,9999.999999999938,19047.619047618962,14265.979386465831,3858723.1495840447,268943.24853228877,164214.3627346574 +1.2,1.05,0.6,0.05,1,1060566.065588159,-11.632039949514507,0.0006575342465753425,0.0011214291612252074,-11.632286524856971,0.0,0.0,332,358,9999.999999999938,19047.619047618962,20541.210744629978,4287346.649838149,268891.0632746242,170372.99637044614 +1.2,1.05,0.6,0.05,2,821315.6745852293,-11.867669603512502,0.00043835616438356166,0.00037739726919168464,-11.867833987074144,0.0,0.0,322,350,9999.999999999938,19047.619047618962,19391.68771405417,4365889.8678374775,268943.24853228877,169335.07812367528 +1.2,1.05,0.6,0.05,3,946164.7720533784,-9.522968680739682,0.0,0.0,-9.522968680739682,0.0,0.0,317,358,9999.999999999938,19046.19226992767,15747.79082813177,3584322.8935798807,269046.1922699269,165747.79082813193 +1.2,1.05,0.6,0.05,4,1608225.108036417,-17.5897916245177,0.0,0.0,-17.5897916245177,0.0,0.0,324,356,9999.999999999938,19047.619047618962,47094.36581846599,6273263.874839215,269047.61904761824,197094.36581846606 +1.2,1.05,0.9,0.03,0,1362467.466812535,-14.722450008531668,0.0,0.0,-14.722450008531668,0.0,0.0,347,356,9999.999999999938,15153.807195097555,957.2993185340194,5317483.336177197,265153.8071950973,150957.29931853397 +1.2,1.05,0.9,0.03,1,2649525.2343291556,-31.297847814202473,0.00021917808219178083,0.0008760914559866876,-31.297930005983297,0.0,0.0,335,354,9999.999999999938,19046.97775702013,19923.58273735027,10842615.938067494,268994.7924993547,169792.16901895223 +1.2,1.05,0.9,0.03,2,594507.8166521876,-13.563431974383363,0.0,0.0,-13.563431974383363,0.0,0.0,338,353,9999.999999999938,12555.772839204219,140.2980305766307,4931143.991461092,262555.7728392041,150140.29803057664 +1.2,1.05,0.9,0.03,3,211682.8254749287,-8.947664788315295,0.0,0.0,-8.947761038632018,0.0,0.0,337,357,9342.914018031315,5270.027389090534,36.66679720587084,3391897.843456463,255270.0273890905,150036.6667972059 +1.2,1.05,0.9,0.03,4,412431.8415874719,-13.876388546807867,3.400157598602041e-05,0.0,-13.87655293036951,0.0,0.0,334,358,9999.999999999938,11740.14793078718,777.4860470755357,5035462.848935928,261732.05231745692,150777.48604707554 +1.2,1.05,0.9,0.04,0,104626.48518273153,-4.56090546131714,0.0,0.0,-4.561087172186893,0.0,0.0,339,352,7500.0665221748895,3555.123723524039,755.0060380911012,1927801.886961217,253555.12372352387,150755.00603809112 +1.2,1.05,0.9,0.04,1,770103.4656279128,-12.353925532770688,0.00021917808219178083,1.7818210590419548e-05,-12.35400772455151,0.0,0.0,345,360,9999.999999999938,17441.50062533865,2280.3573694375345,4527975.177590207,267389.3153676738,152277.68463784902 +1.2,1.05,0.9,0.04,2,1078916.674669085,-12.632186996048942,0.0006575342465753425,0.0006093467952765809,-12.632433571391406,0.0,0.0,347,364,9999.999999999938,14688.80327706154,3103.397953746083,4620728.998682968,264532.247504067,153011.99593445467 +1.2,1.05,0.9,0.04,3,2778400.4051040225,-63.26878141661039,0.0,0.0,-63.26878141661039,0.0,0.0,337,358,9999.999999999938,19047.619047618962,102284.7744686908,21499593.80553689,269047.6190476181,252284.77446869074 +1.2,1.05,0.9,0.04,4,1862692.9279337404,-43.64384217331488,0.0,0.0,-43.64384217331488,0.0,0.0,325,355,9999.999999999938,19047.619047618962,62063.029838266615,14957947.391105017,269047.61904761824,212063.02983826655 +1.2,1.05,0.9,0.05,0,3298318.243312194,-20.50679920849229,0.0,0.0,-20.50679920849229,0.0,0.0,337,352,9999.999999999938,19039.51205219767,28870.82462981034,7245599.736164064,269039.5120521969,178870.82462981055 +1.2,1.05,0.9,0.05,1,398088.33463352616,-9.424405378243682,0.0,0.0,-9.424405378243682,0.0,0.0,340,356,9999.999999999938,15467.686648155943,3201.89188597196,3551468.459414551,265467.6866481556,153201.89188597197 +1.2,1.05,0.9,0.05,2,963789.1784681997,-19.11003599765408,0.00021917808219178083,0.0003244842728010079,-19.1101181894349,0.0,0.0,344,355,9999.999999999938,19044.1606970391,20633.77497965582,6780011.999217983,268991.9754393736,170585.10233873577 +1.2,1.05,0.9,0.05,3,927130.8460164511,-19.814405242937475,0.0,0.0,-19.814405242937475,0.0,0.0,340,349,9999.999999999938,19047.619047618962,23635.521392712693,7014801.747645791,269047.6190476182,173635.52139271275 +1.2,1.05,0.9,0.05,4,1546734.6796252602,-19.61090137614282,0.00043835616438356166,0.0016808443844926252,-19.611065759704463,0.0,0.0,345,353,9999.999999999938,19047.619047618962,28489.945568443036,6946967.125380905,268943.2485322888,178237.8189107691 +1.2,1.2,0.3,0.03,0,1229626.3710968741,-4.760333061270744,0.001095890410958904,0.0008413170055264391,-4.7607440201748545,0.0,0.0,275,334,9999.999999999938,16666.666666666606,8517.77680391485,1996777.687090248,266438.35616438347,158391.579253086 +1.2,1.2,0.3,0.03,1,1026228.7095214186,-4.280648749548314,0.0002191780821917808,6.129320663535841e-05,-4.280730941329137,0.0,0.0,275,328,9999.999999999938,16277.837163699494,3442.91243294119,1836882.916516109,266232.17506324354,153433.71845194598 +1.2,1.2,0.3,0.03,2,1763386.2942673026,-6.165027520312582,0.0004383561643835616,0.0009253033661307286,-6.165191903874226,0.0,0.0,284,321,9999.999999999938,16666.666666666606,15381.405206151852,2465009.1734375316,266575.3424657533,165242.6097012322 +1.2,1.2,0.3,0.03,3,1490537.7150242822,-4.604457073263766,0.0006575342465753425,0.0009040236891646877,-4.6047036486062325,0.0,0.0,285,344,9999.999999999938,16666.666666666606,7599.986602513389,1944819.024421258,266529.6803652966,157464.38304913873 +1.2,1.2,0.3,0.03,4,1048914.9269729382,-4.347534989246468,0.0004383561643835616,0.0001337567062532371,-4.347699372808113,0.0,0.0,285,335,9999.999999999938,16666.639033217805,4049.875654560122,1859178.3297488247,266575.31483230484,154029.81214862192 +1.2,1.2,0.3,0.04,0,1217182.5227755997,-5.031177038774757,0.0,0.0,-5.031177038774757,0.0,0.0,284,339,9999.999999999938,16666.666666666606,17237.957178805664,2087059.0129249226,266666.6666666666,167237.9571788054 +1.2,1.2,0.3,0.04,1,948815.58106802,-3.925888381511747,0.0,0.0,-3.925888381511747,0.0,0.0,296,329,9999.999999999938,16666.666666666606,10998.540520448443,1718629.4605039153,266666.6666666668,160998.5405204483 +1.2,1.2,0.3,0.04,2,1004032.7735586304,-3.697532966697351,0.0,0.0,-3.697532966697351,0.0,0.0,281,330,9999.999999999938,16666.666666666606,10541.660855117156,1642510.9888991164,266666.6666666668,160541.66085511725 +1.2,1.2,0.3,0.04,3,534699.8269831865,-3.828716431757927,0.0,0.0,-3.828716431757927,0.0,0.0,302,343,9999.999999999938,15931.710409334932,4143.577651734896,1686238.810585974,265931.71040933527,154143.5776517349 +1.2,1.2,0.3,0.04,4,719069.5737525197,-3.8880170928443736,0.0,0.0,-3.8880170928443736,0.0,0.0,283,336,9999.999999999938,16666.666666666606,8762.520320389138,1706005.6976147913,266666.66666666616,158762.5203203893 +1.2,1.2,0.3,0.05,0,827684.6992429032,-3.785490551832436,0.0,0.0,-3.785490551832436,0.0,0.0,283,330,9999.999999999938,16666.666666666606,17537.93868438922,1671830.1839441452,266666.6666666665,167537.9386843893 +1.2,1.2,0.3,0.05,1,1152251.0476113423,-5.696488400412018,0.0002191780821917808,0.0005589387376372029,-5.69657059219284,0.0,0.0,283,344,9999.999999999938,16666.666666666606,31443.12179431811,2308829.4668040113,266621.00456621003,181359.2809836726 +1.2,1.2,0.3,0.05,2,807685.0612633582,-3.3910007607184487,0.0002191780821917808,0.0002479048650670226,-3.391082952499271,0.0,0.0,310,352,9999.999999999938,16666.666666666606,14903.337867189535,1540333.5869061474,266621.0045662097,164866.15213742942 +1.2,1.2,0.3,0.05,3,996460.2478916025,-4.995800657523472,0.0006575342465753425,0.0011524233652065792,-4.996047232865938,0.0,0.0,279,330,9999.999999999938,16666.666666666606,27956.496547621602,2075266.8858411605,266529.6803652965,177783.63304284052 +1.2,1.2,0.3,0.05,4,698618.1561099631,-3.133211327424478,0.0,0.0,-3.133211327424478,0.0,0.0,279,344,9999.999999999938,16666.666666666606,11552.884973545553,1454403.7758081527,266666.66666666674,161552.8849735457 +1.2,1.2,0.6,0.03,0,2360447.241400094,-20.958838745748817,0.0,0.0,-20.958838745748817,0.0,0.0,327,341,9999.999999999938,16666.666666666606,29571.24945403215,7396279.581916242,266666.6666666667,179571.24945403222 +1.2,1.2,0.6,0.03,1,627939.8864334992,-8.492448157451689,0.00011455048865243589,0.0,-8.49253034923251,0.0,0.0,332,353,9999.999999999938,12494.017775747505,315.06074213865753,3240816.0524838883,262470.15309061186,150315.06074213862 +1.2,1.2,0.6,0.03,2,706203.1116125098,-11.29911467542106,0.0,0.0,-11.29911467542106,0.0,0.0,314,345,9999.999999999938,15011.332301067927,5043.581276147377,4176371.5584736657,265011.3323010683,155043.5812761473 +1.2,1.2,0.6,0.03,3,290013.44258916285,-6.2345771393625675,0.0,0.0,-6.234645898338198,0.0,0.0,339,355,9975.838509670331,6254.03578468286,62.44199081542834,2488168.218297199,256254.0357846829,150062.4419908154 +1.2,1.2,0.6,0.03,4,332622.5840586738,-3.4649967157245363,0.0,0.0,-3.465150604079128,0.0,0.0,339,353,8407.27793667233,2010.9215518537019,91.04594538259663,1563406.183178185,252010.9215518538,150091.04594538262 +1.2,1.2,0.6,0.04,0,664810.3366077542,-7.577783875218429,0.0008199635239834353,0.0,-7.578112642341715,0.0,0.0,337,360,9999.999999999938,15550.312831545003,2136.518392284083,2935927.9584061396,265379.4870973821,152136.5183922839 +1.2,1.2,0.6,0.04,1,2111861.2495979317,-15.801746654242793,0.0002191780821917808,0.00103029676735562,-15.801828846023614,0.0,0.0,329,343,9999.999999999938,16666.666666666606,28307.652444423926,5677248.88474757,266621.0045662102,178153.10792932074 +1.2,1.2,0.6,0.04,2,1847426.4957219842,-14.059595115228504,0.0,0.0,-14.059595115228504,0.0,0.0,319,343,9999.999999999938,16666.666666666606,24108.580274972202,5096531.705076146,266666.6666666665,174108.58027497222 +1.2,1.2,0.6,0.04,3,835458.582914785,-9.998792987596678,0.0002191780821917808,0.0001092858363961966,-9.9988751793775,0.0,0.0,334,351,9999.999999999938,16629.015425177597,6919.175223136536,3742930.9958655424,266583.35332472104,156902.78234767713 +1.2,1.2,0.6,0.04,4,307571.59117683297,-5.769931885387459,9.195718814232984e-05,0.0,-5.770260652510747,0.0,0.0,330,356,9999.999999999938,10569.095404480622,1136.0490454327812,2333310.6284624934,260549.93765695114,151136.0490454327 +1.2,1.2,0.6,0.05,0,829520.467346847,-13.62606345086243,0.0,0.0,-13.62606345086243,0.0,0.0,326,356,9999.999999999938,16666.666666666606,29301.53129797035,4952021.150287454,266666.6666666665,179301.5312979704 +1.2,1.2,0.6,0.05,1,1365330.629175322,-16.636740096072074,0.0,0.0,-16.636740096072074,0.0,0.0,314,348,9999.999999999938,16666.666666666606,41441.69211359107,5955580.032024007,266666.6666666665,191441.69211359095 +1.2,1.2,0.6,0.05,2,352078.85032255773,-6.789766143958,0.0,0.0,-6.789766143958,0.0,0.0,335,355,9999.999999999938,15190.247909167867,3748.042687656236,2673255.3813193357,265190.2479091682,153748.04268765618 +1.2,1.2,0.6,0.05,3,2594376.4962070696,-15.94691862074577,0.0002191780821917808,0.0018120783227446897,-15.947000812526591,0.0,0.0,329,355,9999.999999999938,16666.666666666606,46378.30896262676,5725639.540248578,266621.0045662102,196106.49721421502 +1.2,1.2,0.6,0.05,4,1223024.6843928257,-11.483495431879948,0.0,0.0,-11.483495431879948,0.0,0.0,327,347,9999.999999999938,16666.666666666606,26414.734374391614,4237831.810626637,266666.6666666667,176414.73437439173 +1.2,1.2,0.9,0.03,0,2493080.7840307285,-23.696898374212925,0.0,0.0,-23.696898374212925,0.0,0.0,337,350,9999.999999999938,16273.792543044208,14805.578418935724,8308966.124737609,266273.7925430439,164805.57841893562 +1.2,1.2,0.9,0.03,1,1111150.028008871,-14.321472947320185,0.0,0.0,-14.321472947320185,0.0,0.0,347,363,9999.999999999938,12806.774785545398,1870.5792184325444,5183824.3157733735,262806.7747855455,151870.5792184326 +1.2,1.2,0.9,0.03,2,504904.86884173285,-16.11315590724273,0.00017013602665623572,0.0,-16.113402482585194,0.0,0.0,344,353,9999.999999999938,13742.14407552572,1932.285047497143,5781051.9690808775,263706.6990699726,151932.2850474971 +1.2,1.2,0.9,0.03,3,161873.3738968452,-9.353376754570776,0.0,0.0,-9.353376754570776,0.0,0.0,347,360,9003.531128195875,5980.33672356577,370.58413280706094,3526795.7826517876,255980.33672356597,150370.58413280707 +1.2,1.2,0.9,0.03,4,683957.5223884184,-17.051565329738743,0.0,0.0,-17.051565329738743,0.0,0.0,337,357,9999.999999999938,14356.451038605055,3565.7015901570153,6093855.109912881,264356.4510386053,153565.701590157 +1.2,1.2,0.9,0.04,0,255445.9004996957,-9.89603002055138,0.0,0.0,-9.89603002055138,0.0,0.0,345,356,9999.999999999938,9477.902471649793,1287.7706609986274,3708676.6735171154,259477.90247165004,151287.7706609986 +1.2,1.2,0.9,0.04,1,409575.25024364964,-11.200898365407753,0.00014317377028589021,0.0,-11.201062748969395,0.0,0.0,345,353,9999.999999999938,12351.555835123274,1990.7960340229504,4143632.7884692308,262321.72796631395,151990.79603402293 +1.2,1.2,0.9,0.04,2,784111.7865316569,-17.697167127668095,0.0002191780821917808,8.324021332414929e-05,-17.697249319448915,0.0,0.0,339,354,9999.999999999938,16214.497436623433,13620.167080618538,6309055.709222662,266168.83533616667,163607.68104861988 +1.2,1.2,0.9,0.04,3,3859829.4827887146,-29.350249273804522,0.0,0.0,-29.350249273804522,0.0,0.0,330,354,9999.999999999938,16666.666666666606,45191.50343766241,10193416.4246015,266666.66666666686,195191.50343766235 +1.2,1.2,0.9,0.04,4,1709368.1167650048,-19.212414988567026,0.0,0.0,-19.212414988567026,0.0,0.0,337,342,9999.999999999938,16644.62722810706,17509.537440304746,6814138.329522314,266644.62722810707,167509.53744030464 +1.2,1.2,0.9,0.05,0,335376.53282847314,-7.639503507114379,8.83986855982822e-05,0.0,-7.639585698895201,0.0,0.0,346,353,9999.999999999938,9439.328059697134,2253.381753164929,2956501.169038122,259420.9116668642,152253.3817531649 +1.2,1.2,0.9,0.05,1,725302.3809721881,-14.883342084047854,0.0002191780821917808,0.00013957623859657114,-14.883424275828675,0.0,0.0,353,357,9999.999999999938,16658.825571673242,12910.699080172657,5371114.0280159265,266613.1634712165,162889.76264438307 +1.2,1.2,0.9,0.05,2,652151.792204098,-12.40284917389088,0.0002191780821917808,7.934996369153292e-05,-12.402931365671702,0.0,0.0,342,359,9999.999999999938,16404.434992890667,6614.276088120816,4544283.057963604,266358.77289243386,156602.37359356706 +1.2,1.2,0.9,0.05,3,254133.2861969499,-8.05509016624952,6.571458872538314e-05,0.0,-8.055336741591985,0.0,0.0,342,356,9984.575687916293,10409.866137388557,3872.358612633846,3095014.631104415,260396.1755980712,153872.35861263392 +1.2,1.2,0.9,0.05,4,508578.3348146373,-23.450572277304776,0.0,0.0,-23.450572277304776,0.0,0.0,339,352,9999.999999999938,16633.86755231754,35544.811222010794,8226857.425768224,266633.86755231745,185544.81122201093 +1.2,1.35,0.3,0.03,0,901939.8575305198,-3.9620758835571785,0.00043835616438356166,5.545903409747173e-05,-3.9622402671188226,0.0,0.0,307,340,9999.999999999938,13986.796805080266,1696.8465465932527,1730691.9611857252,263905.6197376012,151688.52769147867 +1.2,1.35,0.3,0.03,1,853376.6450035879,-3.95205507412544,0.0006575342465753425,2.5447894998342093e-05,-3.9523016494679064,0.0,0.0,304,339,9999.999999999938,14699.923488740513,2650.3322934290027,1727351.6913751466,264578.1578875222,152646.51510917928 +1.2,1.35,0.3,0.03,2,575609.2118971266,-3.5672895381352263,0.00010667619597213488,0.0,-3.567371729916048,0.0,0.0,301,339,9999.999999999938,12116.422019948552,365.37359904567046,1599096.5127117399,262096.66716884216,150365.37359904576 +1.2,1.35,0.3,0.03,3,990356.9313957914,-4.518317969618726,0.0,0.0,-4.518317969618726,0.0,0.0,292,324,9999.999999999938,14774.670219947697,5759.415726922436,1916105.9898729105,264774.67021994665,155759.4157269225 +1.2,1.35,0.3,0.03,4,508964.6381517677,-3.2632048313849054,0.0,0.0,-3.2632048313849054,0.0,0.0,303,337,9999.999999999938,10831.904333314396,204.72130664779297,1497734.9437949655,260831.90433331428,150204.72130664773 +1.2,1.35,0.3,0.04,0,715271.9191727488,-4.599773441415742,0.00021917808219178083,6.029649855394709e-05,-4.599855633196563,0.0,0.0,287,332,9999.999999999938,14814.814814814868,10852.803016110829,1943257.813805251,264774.22628107463,160843.75854132764 +1.2,1.35,0.3,0.04,1,910923.7875508679,-4.952647784462399,0.00021917808219178083,0.00020657207066725918,-4.952729976243221,0.0,0.0,288,333,9999.999999999938,14814.814814814868,15869.092738319598,2060882.5948208023,264774.2262810748,165838.10692771955 +1.2,1.35,0.3,0.04,2,1052251.3666080283,-4.554538742019809,0.00012717290103630196,0.0,-4.554620933800631,0.0,0.0,302,339,9999.999999999938,14814.814814814868,15662.59702151217,1928179.5806732737,264791.2642775852,165662.59702151216 +1.2,1.35,0.3,0.04,3,1115076.2208700778,-4.376061070464417,0.0006575342465753425,0.0009945741664715703,-4.376307645806883,0.0,0.0,289,331,9999.999999999938,14814.814814814868,17239.75711081775,1868687.023488139,264693.0492135965,167090.57098584704 +1.2,1.35,0.3,0.04,4,431683.96002010896,-2.4953495319903634,0.0,0.0,-2.4953495319903634,0.0,0.0,315,349,9999.999999999938,11589.229498353074,1356.1938671023008,1241783.1773301181,261589.22949835286,151356.1938671023 +1.2,1.35,0.3,0.05,0,1663268.084325399,-4.82057488040381,0.0,0.0,-4.82057488040381,0.0,0.0,293,337,9999.999999999938,14814.814814814868,34988.79297402206,2016858.2934679387,264814.81481481413,184988.79297402204 +1.2,1.35,0.3,0.05,1,982837.5416869062,-5.173280231628656,0.0,0.0,-5.173280231628656,0.0,0.0,291,330,9999.999999999938,14814.814814814868,28287.23014124354,2134426.7438762244,264814.81481481384,178287.23014124343 +1.2,1.35,0.3,0.05,2,1058409.613804676,-4.239322588859573,0.0,0.0,-4.239322588859573,0.0,0.0,290,330,9999.999999999938,14814.814814814868,27291.632838992624,1823107.52961986,264814.81481481396,177291.63283899272 +1.2,1.35,0.3,0.05,3,1418746.2193162704,-5.36020840463646,0.00021917808219178083,0.0008365537989960513,-5.360290596417283,0.0,0.0,286,330,9999.999999999938,14814.814814814868,36014.97132047456,2196736.134878826,264774.22628107475,185889.48825062535 +1.2,1.35,0.3,0.05,4,568241.0745814134,-5.4274723703635726,0.00021917808219178083,6.13746722836495e-05,-5.427554562144395,0.0,0.0,283,333,9999.999999999938,14814.814814814868,24718.73643609255,2219157.456787864,264774.22628107487,174709.5302352499 +1.2,1.35,0.6,0.03,0,683362.19384717,-7.454521940444371,0.0,0.0,-7.454521940444371,0.0,0.0,323,349,9999.999999999938,10201.82392140495,212.5014942597303,2894840.6468147878,260201.8239214048,150212.5014942597 +1.2,1.35,0.6,0.03,1,881991.3837074077,-6.861050025854423,0.0,0.0,-6.861050025854423,0.0,0.0,322,346,9999.999999999938,10200.244754989384,609.054131298216,2697016.6752848118,260200.2447549892,150609.0541312982 +1.2,1.35,0.6,0.03,2,496917.3833981176,-9.237086183626639,7.10251946860844e-05,0.0,-9.23716837540746,0.0,0.0,326,342,9999.999999999938,11719.324650830848,1373.520334145782,3489028.727875537,261706.17183700003,151373.5203341457 +1.2,1.35,0.6,0.03,3,378471.63505432196,-7.901736268384115,0.0,0.0,-7.901736268384115,0.0,0.0,332,349,9999.999999999938,9077.194999162415,786.9059340324994,3043912.089461363,259077.19499916225,150786.90593403246 +1.2,1.35,0.6,0.03,4,686367.8150772427,-11.512758028086896,0.0,0.0,-11.512758028086896,0.0,0.0,326,348,9999.999999999938,14349.422114603041,5921.981339184109,4247586.009362281,264349.4221146023,155921.98133918407 +1.2,1.35,0.6,0.04,0,1923130.2142795566,-14.893971813235847,0.0006575342465753425,0.002753157220270791,-14.894218388578311,0.0,0.0,327,350,9999.999999999938,14814.814814814868,34150.36560585222,5374657.271078597,264693.0492135963,183737.39202281178 +1.2,1.35,0.6,0.04,1,444121.38728057704,-5.153875371272002,0.00011336739482115922,0.0,-5.153957563052824,0.0,0.0,336,350,9999.999999999938,8306.220125739474,1123.9378513324161,2127958.4570906744,258285.22616373538,151123.9378513325 +1.2,1.35,0.6,0.04,2,2862405.0441321475,-15.00684026449746,0.00043835616438356166,0.0029812392848336316,-15.007004648059104,0.0,0.0,319,339,9999.999999999938,14814.814814814868,34597.83902886105,5412280.088165803,264733.6377473361,184150.65313613613 +1.2,1.35,0.6,0.04,3,1397510.7094288045,-12.40611785631546,0.0,0.0,-12.40611785631546,0.0,0.0,332,344,9999.999999999938,14814.814814814868,21613.2512667043,4545372.618771795,264814.81481481437,171613.25126670426 +1.2,1.35,0.6,0.04,4,474247.9261400268,-8.264489896542283,0.0,0.0,-8.264489896542283,0.0,0.0,330,350,9999.999999999938,13384.440031790684,5727.3192248236055,3164829.965514082,263384.4400317899,155727.31922482364 +1.2,1.35,0.6,0.05,0,723477.966278476,-13.041548440756264,0.0,0.0,-13.041548440756264,0.0,0.0,313,342,9999.999999999938,14814.814814814868,31949.830277211175,4757182.81358539,264814.8148148141,181949.83027721144 +1.2,1.35,0.6,0.05,1,1277588.2237673565,-13.492000890503798,0.00043835616438356166,0.00129043971102169,-13.49216527406544,0.0,0.0,322,338,9999.999999999938,14814.814814814868,37205.625023831315,4907333.630167913,264733.6377473357,187012.05906717794 +1.2,1.35,0.6,0.05,2,1623223.3135537964,-10.867564509270492,0.0,0.0,-10.867564509270492,0.0,0.0,330,345,9999.999999999938,14814.814814814868,26927.62523306085,4032521.5030901404,264814.81481481437,176927.62523306094 +1.2,1.35,0.6,0.05,3,1012411.7472622467,-10.183231167324411,0.0006575342465753425,0.0012709470183488423,-10.183477742666875,0.0,0.0,334,346,9999.999999999938,14814.814814814868,21634.318420930926,3804410.3891081233,264693.0492135965,171443.67636817857 +1.2,1.35,0.6,0.05,4,2653622.388071688,-17.117902561452,0.0,0.0,-17.117902561452,0.0,0.0,313,337,9999.999999999938,14814.814814814868,61742.927686414114,6115967.520483975,264814.8148148142,211742.92768641433 +1.2,1.35,0.9,0.03,0,709877.5569213026,-12.697500869729355,0.00013351680497346245,0.0,-12.697583061510176,0.0,0.0,347,360,9999.999999999938,10406.909418530588,852.9191495350923,4642500.289909757,260382.18408427617,150852.91914953518 +1.2,1.35,0.9,0.03,1,256480.36176245936,-9.53644394329472,0.0,0.0,-9.53644394329472,0.0,0.0,343,356,9630.915416430453,5702.880884654095,339.86598413315744,3588445.563181336,255702.88088465406,150339.86598413315 +1.2,1.35,0.9,0.03,2,2006738.3678218066,-29.113884994328366,0.0,0.0,-29.113884994328366,0.0,0.0,331,346,9999.999999999938,14814.814814814868,24793.746509753753,10114628.331442803,264814.81481481413,174793.74650975355 +1.2,1.35,0.9,0.03,3,7591536.033119215,-75.41431907110278,0.00021917808219178083,0.003394400186984487,-75.4144012628836,0.0,0.0,329,357,9999.999999999938,14809.730427577224,102656.4563740449,25548106.357034396,264769.1418938373,252147.29634599705 +1.2,1.35,0.9,0.03,4,141529.83830199266,-8.813853796156968,0.0,0.0,-8.814015392966114,0.0,0.0,344,358,9115.194638969457,6111.655998023957,104.20107003351214,3347066.460024625,256111.65599802363,150104.2010700335 +1.2,1.35,0.9,0.04,0,549989.2676047096,-13.000392139302878,0.0,0.0,-13.000392139302878,0.0,0.0,341,355,9999.999999999938,14020.570511683743,3482.423928528402,4743464.046434269,264020.5705116833,153482.42392852847 +1.2,1.35,0.9,0.04,1,104301.77688841324,-10.377778776931379,0.0,0.0,-10.377778776931379,0.0,0.0,345,359,9267.170834011282,9142.087870524883,5025.929343507945,3868526.7631444596,259142.0878705246,155025.9293435079 +1.2,1.35,0.9,0.04,2,1004742.887747182,-21.270742355250807,0.0,0.0,-21.270742355250807,0.0,0.0,343,351,9999.999999999938,14814.814814814868,20761.488948491457,7500247.45175024,264814.81481481413,170761.48894849134 +1.2,1.35,0.9,0.04,3,243201.81980443222,-10.349116139964831,0.0,0.0,-10.349116139964831,0.0,0.0,335,349,9919.107905434254,8274.219610411037,3835.625426534347,3859624.487893686,258274.21961041086,153835.62542653433 +1.2,1.35,0.9,0.04,4,1080907.1611906078,-17.73679917249641,0.0,0.0,-17.73679917249641,0.0,0.0,346,356,9999.999999999938,14814.382350295627,13934.026729435029,6322266.3908321075,264814.3823502949,163934.02672943493 +1.2,1.35,0.9,0.05,0,465482.93250907504,-9.942301703583743,0.00034497256331970824,0.0,-9.942466087145387,0.0,0.0,337,352,9999.999999999938,9611.682933921275,6158.912143961336,3724100.5678612175,259547.79912589898,156158.91214396118 +1.2,1.35,0.9,0.05,1,669142.779856835,-14.75072330986399,0.0,0.0,-14.75072330986399,0.0,0.0,342,349,9999.999999999938,14759.906388229296,13670.997495340458,5326907.769954636,264759.9063882286,163670.99749534042 +1.2,1.35,0.9,0.05,2,265508.3737063394,-18.65593549719702,4.6699936193749586e-05,0.0,-18.65601768897784,0.0,0.0,339,354,9999.999999999938,14167.535996245924,26130.24872377794,6628645.165732306,264158.88785991335,176130.24872377794 +1.2,1.35,0.9,0.05,3,387992.47570267145,-10.798869704571372,0.00024037032918690945,0.0,-10.799034088133014,0.0,0.0,349,358,9999.999999999938,13189.760816007994,6661.364518162168,4009623.234857108,263145.24779208424,156661.36451816215 +1.2,1.35,0.9,0.05,4,4467232.888230735,-19.50185346816305,0.0,0.0,-19.50185346816305,0.0,0.0,343,349,9999.999999999938,14814.814814814868,37571.47760928128,6910617.822720989,264814.8148148144,187571.47760928137 +1.5,1.05,0.3,0.03,0,1556240.6808194614,-6.534600488598989,0.0,0.0,-6.534600488598989,0.0,0.0,275,337,8000.000000000037,19042.827924113677,9734.570939097803,2150560.130293056,269042.827924113,159734.57093909767 +1.5,1.05,0.3,0.03,1,1415512.5237311132,-5.291303213682694,0.0,0.0,-5.291303213682694,0.0,0.0,277,335,8000.000000000037,19047.619047618962,8036.2429578799465,1819014.1903153795,269047.6190476181,158036.24295787993 +1.5,1.05,0.3,0.03,2,1299409.0711047743,-5.854406009391197,0.0006575342465753425,0.0005947240149781616,-5.854652584733662,0.0,0.0,269,344,8000.000000000037,19047.619047618962,5404.760474111172,1969174.9358376462,268891.0632746241,155315.55187186442 +1.5,1.05,0.3,0.03,3,843562.5433904909,-4.28205204174868,0.0,0.0,-4.28205204174868,0.0,0.0,286,341,8000.000000000037,17565.522387637888,407.66276369152104,1549880.5444663132,267565.52238763747,150407.66276369165 +1.5,1.05,0.3,0.03,4,715112.3486046253,-4.5905259803025,0.00043743091989818626,0.0,-4.590772555644965,0.0,0.0,261,334,8000.000000000037,16377.720789338553,1466.611174449197,1632140.2614139935,266273.5705703146,151466.61117444924 +1.5,1.05,0.3,0.04,0,752667.474317552,-4.568455439206858,0.0008767123287671233,0.00017672644471792485,-4.568784206330144,0.0,0.0,284,340,8000.000000000037,19047.619047618962,7662.390350009274,1626254.7837884915,268838.8780169593,157635.8813833016 +1.5,1.05,0.3,0.04,1,925456.4701807607,-7.398426083315503,0.0010958904109589042,0.0008193003549060777,-7.39883704221961,0.0,0.0,275,340,8000.000000000037,19047.619047618962,20482.959924410025,2380913.6222174536,268786.69275929464,170360.06487117434 +1.5,1.05,0.3,0.04,2,1157008.7801997762,-8.16342526333971,0.0,0.0,-8.16342526333971,0.0,0.0,273,327,8000.000000000037,19047.619047618962,25426.537614449837,2584913.4035572466,269047.61904761824,175426.53761444986 +1.5,1.05,0.3,0.04,3,636088.5610851301,-4.65011088797133,0.00019879591817592484,0.0,-4.650193079752151,0.0,0.0,286,349,8000.000000000037,18919.500444592486,5856.110251266124,1648029.5701256837,268872.16808312153,155856.110251266 +1.5,1.05,0.3,0.04,4,698027.5629483585,-5.321336867678038,0.0,0.0,-5.321336867678038,0.0,0.0,273,333,8000.000000000037,19042.861217146932,9775.320657751356,1827023.1647141355,269042.8612171461,159775.32065775152 +1.5,1.05,0.3,0.05,0,918460.3301618433,-6.40134906497799,0.0,0.0,-6.40134906497799,0.0,0.0,284,333,8000.000000000037,19047.619047618962,25205.223375871345,2115026.417327452,269047.6190476181,175205.2233758716 +1.5,1.05,0.3,0.05,1,726164.9082303136,-4.688688805398377,0.0,0.0,-4.688688805398377,0.0,0.0,285,336,8000.000000000037,19047.619047618962,16471.59509769693,1658317.0147728957,269047.6190476182,166471.59509769696 +1.5,1.05,0.3,0.05,2,678423.4401907653,-4.138094765405265,0.0,0.0,-4.138094765405265,0.0,0.0,271,347,8000.000000000037,19047.619047618962,12398.290088731152,1511491.9374414014,269047.6190476181,162398.29008873107 +1.5,1.05,0.3,0.05,3,1271460.149017174,-6.6250385488202745,0.0,0.0,-6.6250385488202745,0.0,0.0,281,337,8000.000000000037,19047.619047618962,33059.64609519373,2174676.9463520655,269047.6190476181,183059.64609519363 +1.5,1.05,0.3,0.05,4,758195.2479590522,-4.790919591806149,0.00043835616438356166,0.00036455798466775396,-4.791083975367792,0.0,0.0,287,342,8000.000000000037,19047.619047618962,17145.99559098996,1685578.5578149685,268943.24853228877,167091.31189328985 +1.5,1.05,0.6,0.03,0,1316067.4354421399,-15.273276893850875,0.0,0.0,-15.273276893850875,0.0,0.0,313,351,8000.000000000037,18791.482458732164,7224.666184462879,4480873.838360254,268791.48245873145,157224.66618446293 +1.5,1.05,0.6,0.03,1,1075733.7569897245,-17.334531352164618,0.0,0.0,-17.334531352164618,0.0,0.0,313,353,8000.000000000037,19010.11969161172,9023.201192931661,5030541.693910575,269010.119691611,159023.20119293148 +1.5,1.05,0.6,0.03,2,839841.6918195641,-15.702062278343204,0.0,0.0,-15.702062278343204,0.0,0.0,327,346,8000.000000000037,18925.018583318983,6464.958491151186,4595216.607558208,268925.0185833183,156464.9584911513 +1.5,1.05,0.6,0.03,3,476766.54737223644,-11.293642006839773,7.098321639377294e-05,0.0,-11.293724198620597,0.0,0.0,332,350,8000.000000000037,12751.878121102676,1408.3472864751027,3419637.8684906163,262734.97735529434,151408.34728647507 +1.5,1.05,0.6,0.03,4,490532.83867654327,-8.80841386509164,0.0,0.0,-8.80841386509164,0.0,0.0,319,355,8000.000000000037,10918.987665138706,751.8768341804767,2756910.364024428,260918.98766513835,150751.87683418047 +1.5,1.05,0.6,0.04,0,1155171.1302780397,-18.75117814649529,0.0,0.0,-18.75117814649529,0.0,0.0,317,351,8000.000000000037,19047.619047618962,23742.898058889466,5408314.172398738,269047.6190476182,173742.89805888943 +1.5,1.05,0.6,0.04,1,760683.4652672441,-14.069073241341087,0.00021917808219178083,5.497532957431062e-05,-14.069155433121908,0.0,0.0,326,341,8000.000000000037,19047.619047618962,12845.250318211172,4159752.86435763,268995.4337899535,162837.004018775 +1.5,1.05,0.6,0.04,2,1021491.981479884,-11.006315528915051,0.00021917808219178083,0.0002113565863917409,-11.006397720695874,0.0,0.0,328,352,8000.000000000037,18943.738801026695,5998.718719211699,3343017.4743773495,268891.55354336114,155967.01523125297 +1.5,1.05,0.6,0.04,3,1261402.0359845348,-17.642831104250195,0.0,0.0,-17.642831104250195,0.0,0.0,319,356,8000.000000000037,19047.619047618962,22015.925493208077,5112754.961133392,269047.6190476181,172015.92549320814 +1.5,1.05,0.6,0.04,4,665951.0308909356,-10.348086020904393,0.0005709239994424006,0.0,-10.34833259624686,0.0,0.0,337,352,8000.000000000037,18584.978435970435,3095.3554281429797,3167489.6055745054,268449.0441503886,153095.35542814288 +1.5,1.05,0.6,0.05,0,2393275.5099155013,-25.746563675741225,0.0006575342465753425,0.0048967012087317505,-25.746810251083687,0.0,0.0,305,347,8000.000000000037,19047.619047618962,64274.11285249015,7273750.313530981,268891.063274624,213539.60767118036 +1.5,1.05,0.6,0.05,1,738240.6771130381,-11.2510003918196,0.0,0.0,-11.2510003918196,0.0,0.0,318,354,8000.000000000037,19047.619047618962,13621.30393224723,3408266.771151892,269047.61904761806,163621.30393224736 +1.5,1.05,0.6,0.05,2,782788.6058307724,-9.892118176326496,0.0,0.0,-9.892118176326496,0.0,0.0,326,357,8000.000000000037,18620.56821008043,9675.757353082132,3045898.1803537295,268620.56821007986,159675.7573530822 +1.5,1.05,0.6,0.05,3,522474.24356698553,-9.609506019452562,0.0,0.0,-9.609506019452562,0.0,0.0,334,362,8000.000000000037,19038.18905213936,9315.467381887065,2970534.938520685,269038.1890521386,159315.46738188702 +1.5,1.05,0.6,0.05,4,1078898.1663543058,-10.839559712308889,0.0,0.0,-10.839559712308889,0.0,0.0,316,356,8000.000000000037,19047.619047618962,15624.112670206958,3298549.2566157035,269047.61904761824,165624.11267020687 +1.5,1.05,0.9,0.03,0,1389387.8734933813,-44.61276793408683,0.00021917808219178083,0.0002484875937277708,-44.61285012586766,0.0,0.0,343,357,8000.000000000037,19047.619047618962,26973.778880370755,12304738.1157565,268995.43378995353,176936.50574131185 +1.5,1.05,0.9,0.03,1,414432.2991074248,-16.75164416041526,4.706771939561011e-05,0.0,-16.75172635219608,0.0,0.0,337,355,8000.000000000037,13161.038524371534,189.70183891541987,4875105.109444076,263149.83192451537,150189.70183891535 +1.5,1.05,0.9,0.03,2,1074059.1632776218,-27.714269830313803,0.0,0.0,-27.714269830313803,0.0,0.0,340,351,8000.000000000037,18874.805815720865,9314.835105664632,7798471.954750309,268874.80581572035,159314.83510566477 +1.5,1.05,0.9,0.03,3,4696544.257537193,-53.98654207583049,0.0,0.0,-53.98654207583049,0.0,0.0,337,359,8000.000000000037,19039.300532449433,53064.126386156255,14804411.220221503,269039.30053244875,203064.12638615628 +1.5,1.05,0.9,0.03,4,176092.12091924946,-11.760323796476202,0.0,0.0,-11.760429533583794,0.0,0.0,319,356,7466.226990230613,7655.645242024988,1048.6543586539417,3543552.572717221,257655.64524202474,151048.65435865396 +1.5,1.05,0.9,0.04,0,563195.35002023,-13.086570695998185,0.0,0.0,-13.086570695998185,0.0,0.0,342,359,8000.000000000037,14460.935397139474,1245.8747183300422,3897752.185599532,264460.93539713946,151245.87471833007 +1.5,1.05,0.9,0.04,1,465960.5127057664,-20.383800001344362,0.0002315284327615302,0.0,-20.383964384906005,0.0,0.0,328,353,8000.000000000037,17543.626833447615,8760.44099408486,5843680.000358486,267488.50101612316,158760.44099408484 +1.5,1.05,0.9,0.04,2,188781.84133201762,-13.605294975423028,0.0,0.0,-13.605508934060353,0.0,0.0,341,360,7971.3660887153665,14083.377938984171,2080.1932922953793,4036050.02620154,264083.37793898373,152080.19329229544 +1.5,1.05,0.9,0.04,3,2118056.8840389303,-37.635237411217226,0.00021917808219178083,0.0009134463143500212,-37.63531960299805,0.0,0.0,336,351,8000.000000000037,19047.619047618962,39550.39549618596,10444063.30965793,268995.4337899535,189413.37854903354 +1.5,1.05,0.9,0.04,4,1772608.9090241664,-26.038995851138377,0.0,0.0,-26.038995851138377,0.0,0.0,334,353,8000.000000000037,19031.5140146984,20385.85789672946,7351732.2269702125,269031.51401469775,170385.85789672937 +1.5,1.05,0.9,0.05,0,699009.745985998,-11.808606167696281,0.0,0.0,-11.808606167696281,0.0,0.0,339,353,8000.000000000037,15237.976332466846,5876.045037450592,3556961.6447190056,265237.9763324667,155876.0450374506 +1.5,1.05,0.9,0.05,1,1045667.6371680482,-18.93861071099277,0.0,0.0,-18.93861071099277,0.0,0.0,333,352,8000.000000000037,19047.619047618962,17322.20657591106,5458296.189598071,269047.6190476182,167322.20657591123 +1.5,1.05,0.9,0.05,2,1638488.8878845787,-45.88352927881007,0.00043835616438356166,0.0019377637191552988,-45.883693662371726,0.0,0.0,334,354,8000.000000000037,19047.619047618962,74630.39709417163,12643607.807682725,268943.24853228877,224339.7325362987 +1.5,1.05,0.9,0.05,3,968541.122571217,-23.43100465416412,0.0,0.0,-23.43100465416412,0.0,0.0,334,356,8000.000000000037,19045.442959266555,26064.050804935145,6656267.907777081,269045.4429592658,176064.05080493513 +1.5,1.05,0.9,0.05,4,400647.35398413555,-28.36642418883304,0.0,0.0,-28.36642418883304,0.0,0.0,337,363,8000.000000000037,18574.745668898526,31266.225585845015,7972379.783688757,268574.74566889793,181266.2255858449 +1.5,1.2,0.3,0.03,0,946534.010595094,-4.761780110397202,0.0004383561643835616,0.00012625741606355254,-4.761944493958845,0.0,0.0,290,332,8000.000000000037,15838.213222556065,3673.6457075787666,1677808.0294392486,265746.88902164303,153654.70709516908 +1.5,1.2,0.3,0.03,1,962386.6094379954,-5.536864376796582,0.0,0.0,-5.536864376796582,0.0,0.0,292,330,8000.000000000037,16632.977944180366,4498.7864190992095,1884497.167145749,266632.97794418025,154498.78641909937 +1.5,1.2,0.3,0.03,2,913222.38363442,-5.082978559514056,0.0002191780821917808,4.3026783538614e-05,-5.083060751294878,0.0,0.0,294,337,8000.000000000037,16666.666666666606,3342.7470768509866,1763460.949203743,266621.0045662097,153336.29305932007 +1.5,1.2,0.3,0.03,3,803996.9484213659,-7.195022614362067,0.00041915768635780097,0.0,-7.1951869979237095,0.0,0.0,283,336,8000.000000000037,16592.963270530356,6708.485202464643,2326672.697163204,266505.63875253935,156708.48520246474 +1.5,1.2,0.3,0.03,4,713761.2415970223,-4.696605763485099,0.0016378462147966066,0.0,-4.697509873074136,0.0,0.0,282,325,8000.000000000037,14274.408235243794,2499.9190332891962,1660428.2035960213,263933.1902738284,152499.9190332892 +1.5,1.2,0.3,0.04,0,577255.045576122,-4.179458139688069,0.0001959063239256192,0.0,-4.17954033146889,0.0,0.0,298,340,8000.000000000037,16620.87941177596,5703.2827812646365,1522522.1705834824,266580.06559429155,155703.28278126454 +1.5,1.2,0.3,0.04,1,1418628.2019566656,-5.9139018283330795,0.0002191780821917808,0.0005826977456796099,-5.913984020113901,0.0,0.0,281,344,8000.000000000037,16666.666666666606,19758.859359438487,1985040.48755548,266621.00456621026,169671.45469758657 +1.5,1.2,0.3,0.04,2,891754.8444935158,-5.0506134407932,0.0002191780821917808,0.00018815372415298013,-5.050695632574022,0.0,0.0,276,330,8000.000000000037,16666.666666666606,14934.25846615719,1754830.2508781804,266621.0045662099,164906.0354075344 +1.5,1.2,0.3,0.04,3,1001919.7627163188,-6.385456957322971,0.0,0.0,-6.385456957322971,0.0,0.0,280,333,8000.000000000037,16666.666666666606,17430.931429427557,2110788.5219527823,266666.6666666665,167430.93142942752 +1.5,1.2,0.3,0.04,4,861886.1573836216,-4.8916406665235765,0.0,0.0,-4.8916406665235765,0.0,0.0,277,333,8000.000000000037,16666.666666666606,10552.127945434324,1712437.511072948,266666.6666666664,160552.12794543448 +1.5,1.2,0.3,0.05,0,1127666.7031723012,-4.586399806343214,0.0,0.0,-4.586399806343214,0.0,0.0,284,337,8000.000000000037,16666.666666666606,20879.57534076999,1631039.9483581875,266666.666666667,170879.5753407702 +1.5,1.2,0.3,0.05,1,901195.2570889692,-5.24543649334244,0.0,0.0,-5.24543649334244,0.0,0.0,291,343,8000.000000000037,16666.666666666606,21289.478691762208,1806783.06489131,266666.66666666657,171289.47869176223 +1.5,1.2,0.3,0.05,2,777237.2300747717,-4.998093458166269,0.0006575342465753425,0.0007445350142605319,-4.998340033508734,0.0,0.0,276,334,8000.000000000037,16666.666666666606,21201.191277855192,1740824.9221776673,266529.68036529684,171089.51102571623 +1.5,1.2,0.3,0.05,3,936277.5222103996,-5.467803913615824,0.0,0.0,-5.467803913615824,0.0,0.0,283,339,8000.000000000037,16666.666666666606,24830.45695463148,1866081.0436308775,266666.66666666645,174830.45695463146 +1.5,1.2,0.3,0.05,4,1070063.9243362187,-6.464162905627517,0.0002191780821917808,0.0005170749073318563,-6.464245097408338,0.0,0.0,268,323,8000.000000000037,16666.666666666606,29729.620236477043,2131776.774833997,266621.00456621026,179652.05900037728 +1.5,1.2,0.6,0.03,0,608487.154954647,-9.95379400613374,0.0,0.0,-9.95379400613374,0.0,0.0,328,353,8000.000000000037,12264.299234454144,208.21115672108604,3062345.0683023296,262264.2992344543,150208.21115672114 +1.5,1.2,0.6,0.03,1,362807.0933184791,-7.928597005037327,3.5491634965039596e-05,0.0,-7.92867919681815,0.0,0.0,327,350,8000.000000000037,9494.933101903378,777.5032264639748,2522292.5346766068,259487.53901128567,150777.50322646392 +1.5,1.2,0.6,0.03,2,378307.01180077705,-9.67792321788573,0.0,0.0,-9.67792321788573,0.0,0.0,330,353,8000.000000000037,11152.95054192094,315.4197123946785,2988779.524769529,261152.950541921,150315.4197123947 +1.5,1.2,0.6,0.03,3,709375.5764363955,-14.134785872887997,0.0,0.0,-14.134785872887997,0.0,0.0,314,353,8000.000000000037,16207.086297234284,5549.953007152035,4177276.232770143,266207.08629723446,155549.95300715204 +1.5,1.2,0.6,0.03,4,415777.95696349686,-12.58226762372965,0.0,0.0,-12.58226762372965,0.0,0.0,313,353,8000.000000000037,12816.945367154127,3020.9144931090887,3763271.3663279256,262816.9453671545,153020.914493109 +1.5,1.2,0.6,0.04,0,599712.0322624412,-14.12160781391487,0.0,0.0,-14.12160781391487,0.0,0.0,324,351,8000.000000000037,16666.263220900102,14080.279571520527,4173762.0837106453,266666.2632208998,164080.27957152048 +1.5,1.2,0.6,0.04,1,1221044.0270078303,-9.239273856851826,0.0002191780821917808,0.0004237528068684683,-9.239356048632649,0.0,0.0,323,350,8000.000000000037,16185.96079183664,5988.471193790574,2871806.361827147,266140.2986913798,155924.9082727604 +1.5,1.2,0.6,0.04,2,440252.6276865581,-8.315464558379391,0.00011463072873141719,0.0,-8.315546750160214,0.0,0.0,320,358,8000.000000000037,13374.100756361728,2342.9794855601704,2625457.215567826,263350.21935454296,152342.97948556012 +1.5,1.2,0.6,0.04,3,337172.8494754195,-7.694883020760406,0.00012755521803197147,0.0,-7.6950474043220485,0.0,0.0,326,353,8000.000000000037,13111.149567392982,1331.3830607978507,2459968.805536093,263084.5755636368,151331.3830607979 +1.5,1.2,0.6,0.04,4,980943.5441840324,-9.135646768325516,0.0,0.0,-9.135646768325516,0.0,0.0,326,351,8000.000000000037,16564.34341268135,4159.756806469216,2844172.471553465,266564.34341268125,154159.75680646914 +1.5,1.2,0.6,0.05,0,469440.84919701493,-6.705434659966644,0.0,0.0,-6.705434659966644,0.0,0.0,333,357,8000.000000000037,13452.990261379988,3264.4915988651237,2196115.909324427,263452.9902613802,153264.49159886522 +1.5,1.2,0.6,0.05,1,564381.684556633,-15.443369461272908,0.0,0.0,-15.443369461272908,0.0,0.0,317,342,8000.000000000037,16666.666666666606,27469.322606787817,4526231.856339464,266666.6666666667,177469.32260678787 +1.5,1.2,0.6,0.05,2,619987.7152829854,-15.980168926056107,0.0,0.0,-15.980168926056107,0.0,0.0,328,358,8000.000000000037,16666.666666666606,27600.414324093916,4669378.380281647,266666.66666666657,177600.41432409387 +1.5,1.2,0.6,0.05,3,410375.4139108513,-7.73489437216489,0.0,0.0,-7.73489437216489,0.0,0.0,324,343,8000.000000000037,14855.679642352396,6348.921767709143,2470638.4992439575,264855.67964235257,156348.92176770914 +1.5,1.2,0.6,0.05,4,2255607.4021828473,-29.426311253982554,0.0,0.0,-29.426311253982554,0.0,0.0,305,350,8000.000000000037,16666.666666666606,71511.40740349864,8255016.334395332,266666.66666666657,221511.40740349877 +1.5,1.2,0.9,0.03,0,810105.7383861233,-26.19524670718481,0.0,0.0,-26.19524670718481,0.0,0.0,342,357,8000.000000000037,15788.316955842365,11076.117039660137,7393399.121915927,265788.3169558419,161076.1170396601 +1.5,1.2,0.9,0.03,1,668600.7364374711,-21.727097716491215,0.00031138516743105786,0.0,-21.727262100052855,0.0,0.0,341,351,8000.000000000037,15668.660500067776,5010.91983553225,6201892.724397637,265603.7885901866,155010.91983553226 +1.5,1.2,0.9,0.03,2,5345733.570778483,-41.523160881502626,0.0004383561643835616,0.004372271609563424,-41.52332526506427,0.0,0.0,331,346,8000.000000000037,16666.666666666606,35830.74488430198,11480842.901734035,266575.34246575355,185174.9041428673 +1.5,1.2,0.9,0.03,3,507753.36085304513,-16.28908598527629,0.0,0.0,-16.28908598527629,0.0,0.0,334,358,8000.000000000037,12705.86778609392,1412.9111204219605,4751756.262740363,262705.86778609426,151412.91112042195 +1.5,1.2,0.9,0.03,4,339836.2591704129,-22.541505644852514,4.280378451073776e-05,0.0,-22.541670028414153,0.0,0.0,343,356,8000.000000000037,13623.920370535907,5445.642206616107,6419068.171960642,263615.00291543006,155445.64220661612 +1.5,1.2,0.9,0.04,0,294729.06627007603,-15.52415613651271,7.704565428260535e-05,0.0,-15.524320520074355,0.0,0.0,330,357,8000.000000000037,14390.822754440442,4766.256606059883,4547774.96973674,264374.7715764653,154766.2566060598 +1.5,1.2,0.9,0.04,1,6916964.057683902,-105.76433216216726,0.0,0.0,-105.76433216216726,0.0,0.0,334,354,8000.000000000037,16666.666666666606,158668.22457001265,28611821.90991136,266666.66666666645,308668.2245700126 +1.5,1.2,0.9,0.04,2,1799154.7879353282,-44.27188924994279,0.0002191780821917808,0.0007795027378604141,-44.271971441723615,0.0,0.0,320,348,8000.000000000037,16666.666666666606,50597.52103320238,12213837.133318108,266621.00456621,200480.5956225232 +1.5,1.2,0.9,0.04,3,549105.241247123,-19.649022327906057,0.0005101646555797076,0.0,-19.649268903248515,0.0,0.0,338,362,8000.000000000037,13926.23076091346,9351.677330626713,5647739.287441592,263819.9464576676,159351.67733062673 +1.5,1.2,0.9,0.04,4,1042855.2152466855,-24.53020092082213,0.0,0.0,-24.53020092082213,0.0,0.0,335,350,8000.000000000037,16666.666666666606,20023.46503607245,6949386.912219217,266666.6666666665,170023.46503607236 +1.5,1.2,0.9,0.05,0,1408675.0374948522,-34.14262544344094,0.0002191780821917808,0.0007938967023866718,-34.14270763522176,0.0,0.0,338,354,8000.000000000037,16666.666666666606,45839.39160815189,9512700.118250905,266621.00456620974,195720.3071027938 +1.5,1.2,0.9,0.05,1,1422634.644566752,-26.642713560103026,0.0,0.0,-26.642713560103026,0.0,0.0,336,361,8000.000000000037,16666.666666666606,34308.22624949403,7512723.616027439,266666.66666666645,184308.22624949404 +1.5,1.2,0.9,0.05,2,2019819.5516681473,-25.33583018227915,0.0,0.0,-25.33583018227915,0.0,0.0,334,345,8000.000000000037,16666.666666666606,37074.81407905732,7164221.381941078,266666.66666666645,187074.8140790572 +1.5,1.2,0.9,0.05,3,148949.71547205446,-5.698740340851294,0.0,0.0,-5.699018349863049,0.0,0.0,341,357,7787.523615986246,4990.394036373969,1347.6416636213894,1927451.6145096575,254990.3940363741,151347.64166362144 +1.5,1.2,0.9,0.05,4,454223.8232668773,-15.938837483804818,0.00033310171117284894,0.0,-15.93900186736646,0.0,0.0,331,348,8000.000000000037,16353.979926739683,10740.589807800437,4658356.662347966,266284.58373691206,160740.5898078006 +1.5,1.35,0.3,0.03,0,631643.2960649228,-3.4413416859764685,0.00015870351674606895,0.0,-3.4414238777572903,0.0,0.0,274,339,8000.000000000037,11981.739119501412,560.4188822756425,1325691.1162603898,261952.34957936293,150560.41888227564 +1.5,1.35,0.3,0.03,1,1012754.5593683904,-5.860194814788562,0.0,0.0,-5.860194814788562,0.0,0.0,285,342,8000.000000000037,14814.814814814868,7004.357437467113,1970718.6172769428,264814.81481481425,157004.35743746706 +1.5,1.35,0.3,0.03,2,1490650.96894021,-7.468316780308536,0.00021917808219178083,0.0003974199423816753,-7.468398972089358,0.0,0.0,271,321,8000.000000000037,14814.814814814868,18365.035241231486,2399551.141415601,264774.22628107463,168305.42224987413 +1.5,1.35,0.3,0.03,3,778178.6986098213,-3.8154966962781383,0.00021917808219178083,2.0039439662870963e-06,-3.81557888805896,0.0,0.0,279,330,8000.000000000037,14071.204730829995,1956.82420706993,1425465.7856741697,264030.6161970903,151956.52361547502 +1.5,1.35,0.3,0.03,4,1190847.7776240157,-5.061137264269021,0.0,0.0,-5.061137264269021,0.0,0.0,290,341,8000.000000000037,14814.814814814868,6582.649880348233,1757636.603805066,264814.8148148144,156582.64988034812 +1.5,1.35,0.3,0.04,0,1113604.3654260691,-5.847383124298679,0.0,0.0,-5.847383124298679,0.0,0.0,251,317,8000.000000000037,14814.814814814868,25787.074644572804,1967302.1664796409,264814.8148148139,175787.074644573 +1.5,1.35,0.3,0.04,1,974515.2665810507,-5.5019672925518135,0.0,0.0,-5.5019672925518135,0.0,0.0,280,330,8000.000000000037,14814.814814814868,18011.69442164347,1875191.2780138121,264814.814814814,168011.69442164362 +1.5,1.35,0.3,0.04,2,914344.114566487,-5.487994562018326,0.0,0.0,-5.487994562018326,0.0,0.0,285,324,8000.000000000037,14814.814814814868,16509.212171907602,1871465.216538214,264814.8148148141,166509.21217190765 +1.5,1.35,0.3,0.04,3,687136.7739128623,-3.7319142282166444,0.0,0.0,-3.7319142282166444,0.0,0.0,276,338,8000.000000000037,14783.358393770142,6781.819907541108,1403177.1275244388,264783.3583937691,156781.81990754107 +1.5,1.35,0.3,0.04,4,1133422.927244048,-4.808712976662104,0.0,0.0,-4.808712976662104,0.0,0.0,265,326,8000.000000000037,14814.814814814868,19200.93837514843,1690323.460443225,264814.8148148138,169200.9383751485 +1.5,1.35,0.3,0.05,0,1422865.185194782,-7.560289775362507,0.0,0.0,-7.560289775362507,0.0,0.0,256,332,8000.000000000037,14814.814814814868,42392.650120682876,2424077.2734299903,264814.81481481413,192392.6501206828 +1.5,1.35,0.3,0.05,1,1101175.1382291422,-6.7732582047387835,0.0,0.0,-6.7732582047387835,0.0,0.0,271,334,8000.000000000037,14814.814814814868,33844.96880877283,2214202.1879303325,264814.8148148141,183844.96880877312 +1.5,1.35,0.3,0.05,2,1465075.6049418943,-6.949286270823134,0.0,0.0,-6.949286270823134,0.0,0.0,277,321,8000.000000000037,14814.814814814868,40779.61429569536,2261143.005552826,264814.8148148142,190779.6142956955 +1.5,1.35,0.3,0.05,3,1076470.4977890023,-5.8539381715743275,0.0,0.0,-5.8539381715743275,0.0,0.0,292,334,8000.000000000037,14814.814814814868,28766.86157044975,1969050.1790864822,264814.814814814,178766.8615704496 +1.5,1.35,0.3,0.05,4,699412.260627944,-4.745432879247275,0.00021917808219178083,0.0002194869735573492,-4.745515071028096,0.0,0.0,275,336,8000.000000000037,14814.814814814868,20458.202869341785,1673448.767799268,264774.22628107463,170425.27982330823 +1.5,1.35,0.6,0.03,0,1605210.207797326,-24.553909244850264,0.0,0.0,-24.553909244850264,0.0,0.0,322,337,8000.000000000037,14814.814814814868,30710.781393628786,6955709.131960045,264814.8148148141,180710.78139362892 +1.5,1.35,0.6,0.03,1,374440.741454043,-12.551484369838478,0.0,0.0,-12.551484369838478,0.0,0.0,315,349,8000.000000000037,11793.625903423048,5839.30023393191,3755062.498623609,261793.62590342277,155839.30023393183 +1.5,1.35,0.6,0.03,2,511997.4182964579,-8.18268583657117,0.00020866322167459932,0.0,-8.182850220132815,0.0,0.0,330,348,8000.000000000037,9210.819784194608,683.3581156053941,2590049.5564189646,259172.17844684722,150683.35811560546 +1.5,1.35,0.6,0.03,3,1989105.7775826794,-18.792670457232152,0.0,0.0,-18.792670457232152,0.0,0.0,326,344,8000.000000000037,14814.814814814868,18937.36844309185,5419378.788595235,264814.81481481425,168937.36844309152 +1.5,1.35,0.6,0.03,4,371618.76003334986,-7.9195160865133944,8.877921675388277e-05,0.0,-7.91968047007504,0.0,0.0,333,354,8000.000000000037,7910.416953544193,307.6269670516602,2519870.956403558,257893.97635784888,150307.62696705174 +1.5,1.35,0.6,0.04,0,1996255.5256184838,-17.299916307218346,0.0,0.0,-17.299916307218346,0.0,0.0,324,347,8000.000000000037,14814.814814814868,31119.26003396007,5021311.015258227,264814.81481481425,181119.2600339599 +1.5,1.35,0.6,0.04,1,1802386.812920105,-15.359471363211695,0.0019726027397260278,0.006704010095173311,-15.360211089239098,0.0,0.0,325,356,8000.000000000037,14814.814814814868,21667.020328097384,4503859.030189799,264449.5180111612,170661.41881382134 +1.5,1.35,0.6,0.04,2,516042.8579373668,-12.930226258325543,0.0,0.0,-12.930226258325543,0.0,0.0,331,346,8000.000000000037,14786.178274578848,10051.059523392974,3856060.3355534864,264786.1782745781,160051.059523393 +1.5,1.35,0.6,0.04,3,2361052.128191812,-18.73156085745603,0.00043835616438356166,0.0025339888137851,-18.73172524101767,0.0,0.0,326,353,8000.000000000037,14814.814814814868,34060.70698661178,5403082.895321608,264733.6377473359,183680.60866454398 +1.5,1.35,0.6,0.04,4,700288.7669670478,-11.430315824126081,0.00043835616438356166,0.0001617389678654618,-11.430480207687726,0.0,0.0,326,356,8000.000000000037,14814.135543804992,9711.051202284256,3456084.2197669586,264732.9584763258,159686.7903571043 +1.5,1.35,0.6,0.05,0,2568608.792068357,-22.92158052273063,0.0,0.0,-22.92158052273063,0.0,0.0,310,348,8000.000000000037,14814.814814814868,66072.57782441196,6520421.472728163,264814.814814814,216072.57782441194 +1.5,1.35,0.6,0.05,1,692207.364603816,-12.132231904750984,0.00021917808219178083,0.00019193105012894597,-12.132314096531806,0.0,0.0,338,354,8000.000000000037,14814.814814814868,16761.284785632084,3643261.8412669427,264774.2262810749,166732.49512811276 +1.5,1.35,0.6,0.05,2,811481.545939305,-11.599218143923393,0.0,0.0,-11.599218143923393,0.0,0.0,312,351,8000.000000000037,14814.814814814868,19504.768730161133,3501124.838379572,264814.81481481425,169504.768730161 +1.5,1.35,0.6,0.05,3,1564451.7342416807,-13.077554485249367,0.0,0.0,-13.077554485249367,0.0,0.0,319,349,8000.000000000037,14814.814814814868,25935.688427223115,3895347.8627331746,264814.81481481413,175935.68842722292 +1.5,1.35,0.6,0.05,4,1049681.2348400697,-14.70595580886862,0.0,0.0,-14.70595580886862,0.0,0.0,328,339,8000.000000000037,14814.814814814868,29994.64991000189,4329588.215698318,264814.81481481413,179994.64991000184 +1.5,1.35,0.9,0.03,0,811735.8015167773,-15.200468982293215,0.0,0.0,-15.200468982293215,0.0,0.0,348,356,8000.000000000037,10908.690780361656,637.6461881229728,4461458.395278211,260908.69078036165,150637.64618812292 +1.5,1.35,0.9,0.03,1,963939.4207653644,-25.58322797757842,0.00021917808219178083,6.186498098412438e-05,-25.58331016935924,0.0,0.0,347,357,8000.000000000037,14740.177076505395,12438.72352776306,7230194.127354221,264699.5885427656,162429.4437806154 +1.5,1.35,0.9,0.03,2,538655.5442529976,-32.13264785584309,0.0,0.0,-32.13264785584309,0.0,0.0,334,357,8000.000000000037,14713.461133074792,18349.922617258468,8976706.094891435,264713.46113307413,168349.92261725824 +1.5,1.35,0.9,0.03,3,274680.2575423333,-16.72198622681951,0.0,0.0,-16.72198622681951,0.0,0.0,346,357,8000.000000000037,10600.129034506668,1624.706359404335,4867196.3271518815,260600.12903450654,151624.7063594044 +1.5,1.35,0.9,0.03,4,987929.3398277629,-18.408140921059125,0.00021917808219178083,0.00010633913994161176,-18.408223112839945,0.0,0.0,335,355,8000.000000000037,14343.82762802511,5192.781974222146,5316837.578949105,264303.23909428535,155176.83110323094 +1.5,1.35,0.9,0.04,0,1079879.8589364262,-11.353720837360814,0.001315068493150685,0.0016647123034517863,-11.35421398804575,0.0,0.0,333,355,8000.000000000037,10437.764700463364,4635.573880659255,3435658.8899628804,260194.23349802787,154385.86703514148 +1.5,1.35,0.9,0.04,1,3062509.067277856,-33.07911637165658,0.00021917808219178083,0.0017714797619221318,-33.0791985634374,0.0,0.0,323,352,8000.000000000037,14814.814814814868,39871.808572901566,9229097.69910839,264774.2262810749,189606.08660861355 +1.5,1.35,0.9,0.04,2,2253931.3204153394,-35.23247638324022,0.00021917808219178083,0.0011549440100545503,-35.232558575021045,0.0,0.0,339,354,8000.000000000037,14814.814814814868,37179.97136021497,9803327.035530692,264774.22628107504,187006.7297587068 +1.5,1.35,0.9,0.04,3,1088182.141972312,-12.597561037642688,0.0,0.0,-12.597561037642688,0.0,0.0,335,351,8000.000000000037,12283.234547824379,5198.646632470916,3767349.610038059,262283.2345478242,155198.64663247095 +1.5,1.35,0.9,0.04,4,1079093.1909604075,-25.11289796224781,0.0,0.0,-25.11289796224781,0.0,0.0,338,352,8000.000000000037,14814.814814814868,22020.42926063428,7104772.789932719,264814.81481481425,172020.42926063435 +1.5,1.35,0.9,0.05,0,378661.7143823111,-10.58105113041006,0.0001317265606694524,0.0,-10.581133322190883,0.0,0.0,345,352,7998.1907611655815,9754.472161086469,5652.67988349832,3229611.8255371978,259730.07835355477,155652.67988349844 +1.5,1.35,0.9,0.05,1,391742.3499272718,-11.061922922627538,0.0,0.0,-11.061922922627538,0.0,0.0,347,361,8000.000000000037,14064.704783301215,3523.6739751693367,3357846.112700684,264064.7047833009,153523.67397516943 +1.5,1.35,0.9,0.05,2,896466.292647913,-13.216913307728952,0.00021917808219178083,0.0003726669278470261,-13.216995499509775,0.0,0.0,343,357,8000.000000000037,14702.286962826636,8568.957444154328,3932510.2153943973,264661.698429087,158513.05740497712 +1.5,1.35,0.9,0.05,3,490703.54050765315,-12.409555942125067,0.0002123460972532455,0.0,-12.409638133905888,0.0,0.0,343,353,8000.000000000037,10673.678843440262,9572.651201864635,3717214.917900046,260634.35549209698,159572.65120186456 +1.5,1.35,0.9,0.05,4,1545349.5147053462,-17.36567617142753,0.0,0.0,-17.36567617142753,0.0,0.0,340,356,8000.000000000037,14806.98216196676,21017.095650447452,5038846.979047351,264806.9821619663,171017.09565044745 +1.8,1.05,0.3,0.03,0,912477.4495742149,-5.068636528455309,0.00021917808219178083,2.9777923939955854e-05,-5.068718720236132,0.0,0.0,269,343,6666.666666666613,18134.316026737357,913.7857740303372,1533030.3396567362,268082.13076907245,150909.3190854393 +1.8,1.05,0.3,0.03,1,796180.2109105196,-5.175779266779098,0.00019787168033020012,0.0,-5.175861458559919,0.0,0.0,279,345,6666.666666666613,18327.61995799324,1095.7856004058258,1556839.83706202,268280.5076531522,151095.78560040588 +1.8,1.05,0.3,0.03,2,1430431.0481464467,-7.109273304734236,0.0010958904109589042,0.0013375995401000793,-7.1096842636383455,0.0,0.0,274,335,6666.666666666613,19047.619047618962,8287.849745459709,1986505.1788298283,268786.6927592946,158087.20981444482 +1.8,1.05,0.3,0.03,3,1737630.5198458168,-9.622992160067358,0.00043835616438356166,0.000925179981678433,-9.623156543629003,0.0,0.0,255,322,6666.666666666613,19047.619047618962,20353.522156402945,2545109.3689038567,268943.24853228877,170214.74515915124 +1.8,1.05,0.3,0.03,4,1107998.7735295105,-7.721118883400184,0.0,0.0,-7.721118883400184,0.0,0.0,262,340,6666.666666666613,19047.619047618962,7242.590061570703,2122470.8629778153,269047.61904761824,157242.59006157075 +1.8,1.05,0.3,0.04,0,1188924.2517492552,-9.181398510150025,0.0,0.0,-9.181398510150025,0.0,0.0,278,336,6666.666666666613,19047.619047618962,24587.088564851878,2446977.4467000016,269047.6190476182,174587.08856485185 +1.8,1.05,0.3,0.04,1,1342099.357591711,-7.069739405641032,0.0,0.0,-7.069739405641032,0.0,0.0,260,321,6666.666666666613,19047.619047618962,22526.65678667415,1977719.867920228,269047.6190476183,172526.65678667414 +1.8,1.05,0.3,0.04,2,1153888.853462916,-4.959526477432489,0.00043835616438356166,0.0007310928135560466,-4.959690860994133,0.0,0.0,266,333,6666.666666666613,19047.619047618962,11918.007011385314,1508783.6616516635,268943.2485322887,161808.34308935195 +1.8,1.05,0.3,0.04,3,1205645.0715645512,-6.047240289958698,0.00043835616438356166,0.0008096497883328062,-6.047404673520342,0.0,0.0,284,333,6666.666666666613,19047.619047618962,13252.272469923644,1750497.842213041,268943.24853228877,163130.82500167377 +1.8,1.05,0.3,0.04,4,632250.537866666,-5.781069093455903,0.0,0.0,-5.781069093455903,0.0,0.0,271,339,6666.666666666613,19043.891672990176,7602.7183260476895,1691348.6874346426,269043.89167298947,157602.718326048 +1.8,1.05,0.3,0.05,0,1513103.626270571,-7.61691672608526,0.00043835616438356166,0.0015078133835716866,-7.617081109646904,0.0,0.0,272,340,6666.666666666613,19047.619047618962,36374.35434284997,2099314.828018944,268943.2485322889,186148.18233531414 +1.8,1.05,0.3,0.05,1,954206.2729733045,-6.280811200740151,0.0,0.0,-6.280811200740151,0.0,0.0,271,344,6666.666666666613,19047.619047618962,22731.209312886953,1802402.489053366,269047.6190476182,172731.20931288696 +1.8,1.05,0.3,0.05,2,743599.980737465,-5.545449410554085,0.0,0.0,-5.545449410554085,0.0,0.0,280,349,6666.666666666613,19047.619047618962,17009.2746173337,1638988.757900906,269047.6190476183,167009.27461733363 +1.8,1.05,0.3,0.05,3,1390382.5929852447,-7.0087353413181015,0.0015342465753424657,0.005218656224658718,-7.009310683783854,0.0,0.0,267,342,6666.666666666613,19047.619047618962,33682.99226229001,1964163.4091817986,268682.3222439654,182900.19382859112 +1.8,1.05,0.3,0.05,4,832827.7530907448,-6.5570776767695325,0.0,0.0,-6.5570776767695325,0.0,0.0,271,334,6666.666666666613,19047.619047618962,21081.680585632657,1863795.0392821156,269047.61904761824,171081.6805856326 +1.8,1.05,0.6,0.03,0,607841.5222725265,-11.798061955596992,0.00013104405704626927,0.0,-11.798144147377814,0.0,0.0,337,358,6666.666666666613,13565.405606487404,217.32186867070493,3028458.2123548966,263534.20464052417,150217.32186867073 +1.8,1.05,0.6,0.03,1,1027743.4688483425,-17.902628654731856,0.00021917808219178083,7.99207493023098e-05,-17.90271084651268,0.0,0.0,318,353,6666.666666666613,18983.21251653326,6266.848939514789,4385028.589940443,268931.0272588677,156254.86082711947 +1.8,1.05,0.6,0.03,2,847677.4077423437,-15.707634711909014,0.00021532376251454113,0.0,-15.707716903689835,0.0,0.0,313,354,6666.666666666613,18719.89066019518,3356.8931265819797,3897252.1582020246,268668.6230976911,153356.89312658212 +1.8,1.05,0.6,0.03,3,465632.48026047146,-14.87484911659527,0.0,0.0,-14.87484911659527,0.0,0.0,324,347,6666.666666666613,15774.418127021481,2115.3918356241525,3712188.692576751,265774.418127021,152115.39183562406 +1.8,1.05,0.6,0.03,4,655226.6457742715,-13.698335196370424,0.00014804217943483716,0.0,-13.698417388151245,0.0,0.0,313,350,6666.666666666613,16055.802113639382,698.263007547748,3450741.154749002,266020.55397567875,150698.26300754762 +1.8,1.05,0.6,0.04,0,964021.3418766981,-24.021225748101212,0.0,0.0,-24.021225748101212,0.0,0.0,329,356,6666.666666666613,19047.619047618962,24606.673195343566,5744716.832911414,269047.61904761824,174606.67319534364 +1.8,1.05,0.6,0.04,1,642867.8740932281,-15.325305143805302,0.0006266962524179297,0.0,-15.325551719147771,0.0,0.0,321,355,6666.666666666613,18910.18603997936,9449.52299037005,3812290.031956759,268760.97264654585,159449.52299037023 +1.8,1.05,0.6,0.04,2,310117.5798493155,-10.469854417294965,0.0,0.0,-10.469854417294965,0.0,0.0,326,345,6666.666666666613,13181.655772660193,3003.3612143378455,2733300.9816211076,263181.6557726597,153003.36121433775 +1.8,1.05,0.6,0.04,3,902110.2261837617,-14.05782744450804,0.0,0.0,-14.05782744450804,0.0,0.0,314,352,6666.666666666613,18670.193872097985,8636.424256873273,3530628.32100181,268670.1938720974,158636.4242568733 +1.8,1.05,0.6,0.04,4,1488089.8511891393,-15.873076895896375,0.00021917808219178083,0.000609709928704579,-15.873159087677198,0.0,0.0,327,343,6666.666666666613,19040.66737448224,13942.574620036556,3934017.0879769875,268988.48211681674,163851.11813073087 +1.8,1.05,0.6,0.05,0,1437456.7733147184,-25.600118577824272,0.0008767123287671233,0.003214674335977524,-25.600447344947565,0.0,0.0,307,346,6666.666666666613,19047.619047618962,48170.57393018141,6095581.906183207,268838.87801695947,197688.37277978464 +1.8,1.05,0.6,0.05,1,1391678.7914892617,-15.141392156924375,0.00021917808219178083,0.0007542752955806693,-15.141474348705197,0.0,0.0,324,356,6666.666666666613,19047.619047618962,21279.89500534705,3771420.479316544,268995.43378995353,171166.7537110099 +1.8,1.05,0.6,0.05,2,843080.3402481969,-19.89094897252482,0.00043835616438356166,0.0005759284187181856,-19.891113356086468,0.0,0.0,325,346,6666.666666666613,19047.619047618962,27813.694857762664,4826877.549449994,268943.2485322888,177727.305594955 +1.8,1.05,0.6,0.05,3,420382.886141184,-10.359595108207696,0.00032644484762445197,0.0,-10.35975949176934,0.0,0.0,329,352,6666.666666666613,18287.650861951923,4899.657297123464,2708798.912935042,268209.9258982315,154899.65729712337 +1.8,1.05,0.6,0.05,4,709165.4550934754,-13.908873382382176,0.0,0.0,-13.908873382382176,0.0,0.0,319,348,6666.666666666613,19047.619047618962,17585.015031946026,3497527.4183071647,269047.6190476182,167585.01503194615 +1.8,1.05,0.9,0.03,0,181728.5620991762,-8.25082437209344,0.0,0.0,-8.25082437209344,0.0,0.0,340,351,5460.205348849383,3462.4637726010696,509.18186793268893,2238976.7324807225,253462.46377260113,150509.1818679327 +1.8,1.05,0.9,0.03,1,236300.8876901694,-12.118700396156873,0.0,0.0,-12.118700396156873,0.0,0.0,336,359,6649.0338718052,6957.985428008276,39.6957515979534,3099693.566351122,256957.98542800813,150039.69575159796 +1.8,1.05,0.9,0.03,2,1182455.294163601,-45.09536922992548,0.0,0.0,-45.09536922992548,0.0,0.0,320,360,6666.666666666613,18989.61858432063,20482.28685068241,10427859.828872321,268989.6185843199,170482.28685068229 +1.8,1.05,0.9,0.03,3,1400591.0193979626,-27.238020504379953,0.00021917808219178083,0.00022223834364243028,-27.238102696160777,0.0,0.0,337,360,6666.666666666613,18131.278052900954,4660.772690377168,6459560.11208448,268079.09279523557,154627.43693883074 +1.8,1.05,0.9,0.03,4,346504.2840118109,-15.419520689650655,7.36038960191702e-05,0.0,-15.419685073212301,0.0,0.0,336,358,6666.666666666613,10344.866942535691,823.6828172541574,3833226.819922398,260327.34220538815,150823.68281725422 +1.8,1.05,0.9,0.04,0,539060.9448653533,-25.9986051923042,0.00015974307912647025,0.0,-25.998687384085024,0.0,0.0,337,358,6666.666666666613,17848.384659462165,12704.39854108394,6184134.487178754,267810.35059300304,162704.3985410838 +1.8,1.05,0.9,0.04,1,2203076.9990683543,-41.21983539708227,0.0006575342465753425,0.0028632308068618356,-41.22008197242473,0.0,0.0,337,359,6666.666666666613,19047.619047618962,37382.91130492928,9566630.088240532,268891.0632746242,186953.42668389992 +1.8,1.05,0.9,0.04,2,176429.80573522244,-13.14036235748494,0.0,0.0,-13.14036235748494,0.0,0.0,340,355,6666.666666666613,9876.094325892385,2834.1902109212274,3326747.190552236,259876.09432589222,152834.1902109213 +1.8,1.05,0.9,0.04,3,158409.0811455859,-7.964542043989625,0.0,0.0,-7.964542043989625,0.0,0.0,343,352,6424.502580190691,5034.675194976116,908.3409527341576,2176322.734577878,255034.67519497598,150908.3409527342 +1.8,1.05,0.9,0.04,4,322996.2519648119,-11.757906975446373,0.00013138742237047858,0.0,-11.758071359008019,0.0,0.0,340,358,6666.666666666613,10134.90960077414,762.212501478057,3019534.8834325396,260103.62688116182,150762.21250147803 +1.8,1.05,0.9,0.05,0,986253.2329192726,-30.38432316485243,0.0,0.0,-30.38432316485243,0.0,0.0,332,358,6666.666666666613,19013.90524927493,31903.161151915578,7158738.481078356,269013.9052492742,181903.16115191538 +1.8,1.05,0.9,0.05,1,586014.2255966779,-18.171473201728734,0.00043835616438356166,0.0001141269725896323,-18.171637585290377,0.0,0.0,348,359,6666.666666666613,18208.666009109755,7569.779158836092,4444771.822606421,268104.29549377976,157552.6601129476 +1.8,1.05,0.9,0.05,2,409785.8763967352,-14.81869661682637,0.00013552529306721524,0.0,-14.818778808607194,0.0,0.0,335,361,6666.666666666613,17890.8956837225,4621.545169095678,3699710.3592947703,267858.62775680143,154621.5451690956 +1.8,1.05,0.9,0.05,3,266820.9240522926,-12.773319532707779,0.0,0.0,-12.773319532707779,0.0,0.0,335,358,6666.666666666613,15112.203988815738,1526.1582257998205,3245182.1183795235,265112.2039888156,151526.15822579982 +1.8,1.05,0.9,0.05,4,219847.56094973226,-16.633636340873448,0.0,0.0,-16.633636340873448,0.0,0.0,337,355,6666.666666666613,16421.659274521662,6792.9562571189845,4103030.297971911,266421.65927452117,156792.95625711902 +1.8,1.2,0.3,0.03,0,879530.9582952631,-6.899410425224399,0.0004383561643835616,9.910666686008814e-05,-6.899574808786043,0.0,0.0,277,336,6666.666666666613,16666.666666666606,6007.048942887609,1939868.9833831969,266575.3424657532,155992.18294285855 +1.8,1.2,0.3,0.03,1,1849273.223177738,-10.211796369034134,0.0,0.0,-10.211796369034134,0.0,0.0,252,321,6666.666666666613,16666.666666666606,21478.829457867516,2675954.7486742497,266666.6666666667,171478.8294578677 +1.8,1.2,0.3,0.03,2,1733482.1720467506,-7.788737205073518,0.0,0.0,-7.788737205073518,0.0,0.0,274,335,6666.666666666613,16666.666666666606,15027.053785052261,2137497.1566830017,266666.66666666686,165027.05378505218 +1.8,1.2,0.3,0.03,3,524000.46373774926,-5.474315531423315,0.00011484953832675454,0.0,-5.474397723204136,0.0,0.0,282,337,6666.666666666613,15315.467507512429,1591.6094609290346,1623181.2292051795,265291.5405203613,151591.6094609291 +1.8,1.2,0.3,0.03,4,1190069.526661835,-5.3820821901002995,0.0006575342465753425,0.0006129417905848533,-5.3823287654427645,0.0,0.0,267,329,6666.666666666613,16654.977677688395,4779.983435941152,1602684.9311333976,266517.9913763188,154688.0421673534 +1.8,1.2,0.3,0.04,0,802261.9815721798,-5.896022759138839,0.0,0.0,-5.896022759138839,0.0,0.0,270,336,6666.666666666613,16666.666666666606,13211.747533393342,1716893.9464752958,266666.6666666664,163211.7475333936 +1.8,1.2,0.3,0.04,1,706182.7236933182,-8.380843655610786,0.0008767123287671232,0.00033762626271404816,-8.381172422734073,0.0,0.0,264,344,6666.666666666613,16666.666666666606,18834.653361972458,2269076.3679135055,266484.01826483983,168784.00942256526 +1.8,1.2,0.3,0.04,2,1196358.1050480541,-6.277271905017845,0.0004383561643835616,0.0008521908804429396,-6.277436288579489,0.0,0.0,274,333,6666.666666666613,16666.666666666606,18192.684560211717,1801615.9788928523,266575.34246575343,168064.85592814526 +1.8,1.2,0.3,0.04,3,938470.7926871749,-6.714616518726676,0.0,0.0,-6.714616518726676,0.0,0.0,266,324,6666.666666666613,16666.666666666606,15948.830930271833,1898803.6708281487,266666.6666666666,165948.8309302718 +1.8,1.2,0.3,0.04,4,499745.86616696115,-4.242371315205299,0.0001683154714943062,0.0,-4.24245350698612,0.0,0.0,279,336,6666.666666666613,15579.949770894484,3870.8021858506427,1349415.8478233994,265544.8840476669,153870.80218585068 +1.8,1.2,0.3,0.05,0,1215897.1624413405,-8.132582009784322,0.0002191780821917808,0.000665898053610901,-8.132664201565145,0.0,0.0,275,331,6666.666666666613,16666.666666666606,35797.75275945723,2213907.113285403,266621.0045662097,185697.8680514156 +1.8,1.2,0.3,0.05,1,638072.0122746232,-5.437653439019043,0.0004383561643835616,0.00027652292599791443,-5.437817822580687,0.0,0.0,275,348,6666.666666666613,16666.666666666606,16673.84405680645,1615034.0975597848,266575.34246575343,166632.3656179068 +1.8,1.2,0.3,0.05,2,1122904.4944154106,-5.732203163077527,0.0,0.0,-5.732203163077527,0.0,0.0,277,327,6666.666666666613,16666.666666666606,27702.245639324483,1680489.5917950047,266666.6666666668,177702.24563932448 +1.8,1.2,0.3,0.05,3,1090489.513417872,-7.099693216582995,0.0008767123287671232,0.0020726011579849696,-7.100021983706283,0.0,0.0,263,339,6666.666666666613,16666.666666666606,29820.191241571105,1984376.2703517748,266484.01826484,179509.30106787346 +1.8,1.2,0.3,0.05,4,1593976.8025414185,-7.446572408549424,0.0004383561643835616,0.0020075480620008624,-7.446736792111068,0.0,0.0,265,329,6666.666666666613,16666.666666666606,37504.300423834815,2061460.5352332036,266575.3424657533,187203.1682145347 +1.8,1.2,0.6,0.03,0,778889.0842405421,-11.330372423411271,0.0,0.0,-11.330372423411271,0.0,0.0,320,349,6666.666666666613,11665.328374950028,1795.7865378853785,2924527.2052025134,261665.32837495027,151795.7865378854 +1.8,1.2,0.6,0.03,1,652018.3748917739,-13.433470032294426,0.0,0.0,-13.433470032294426,0.0,0.0,321,351,6666.666666666613,15725.174168783742,1788.108182285203,3391882.229398772,265725.1741687836,151788.108182285 +1.8,1.2,0.6,0.03,2,1035387.9205808433,-17.36149769018281,0.0002191780821917808,0.00012433765734314286,-17.361579881963632,0.0,0.0,310,340,6666.666666666613,16666.666666666606,7399.231576294207,4264777.264485094,266621.00456621,157380.58092769273 +1.8,1.2,0.6,0.03,3,180212.64858669313,-10.85082618496744,0.0,0.0,-10.85082618496744,0.0,0.0,323,356,6600.262465001998,9643.736545790396,1728.771578705292,2817894.9702355517,259643.73654579077,151728.7715787054 +1.8,1.2,0.6,0.03,4,631751.094074592,-15.086372632132347,0.0,0.0,-15.086372632132347,0.0,0.0,319,348,6666.666666666613,14711.64679995547,4888.888932951693,3759193.9182516607,264711.64679995587,154888.88893295158 +1.8,1.2,0.6,0.04,0,560014.3637691197,-14.296656595963285,0.0003983852185849715,0.0,-14.29682097952493,0.0,0.0,313,347,6666.666666666613,16506.762836069993,10642.596116340717,3583701.465769644,266423.76591553167,160642.5961163409 +1.8,1.2,0.6,0.04,1,1118869.6262696,-13.855856101342106,0.0006575342465753425,0.0010569723055100571,-13.856102676684575,0.0,0.0,313,349,6666.666666666613,16479.414432802692,13641.145641540545,3485745.8002982666,266342.42813143303,163482.59979571405 +1.8,1.2,0.6,0.04,2,3732780.633744084,-32.05148480997694,0.0,0.0,-32.05148480997694,0.0,0.0,308,343,6666.666666666613,16666.666666666606,60216.75902562667,7529218.846661577,266666.6666666669,210216.75902562684 +1.8,1.2,0.6,0.04,3,666645.4352385029,-15.988835905384862,0.0002191780821917808,4.4315513713647574e-05,-15.988918097165685,0.0,0.0,312,351,6666.666666666613,16666.666666666606,13606.089302854043,3959741.3123077694,266621.00456620986,163599.44197579706 +1.8,1.2,0.6,0.04,4,1566180.1876311416,-18.445104118908706,0.0002191780821917808,0.0007162586439887925,-18.44518631068953,0.0,0.0,330,345,6666.666666666613,16666.666666666606,21771.720651824522,4505578.693090849,266621.0045662101,171664.28185522617 +1.8,1.2,0.6,0.05,0,4002072.4215429635,-32.788740756748304,0.0002191780821917808,0.003184141588823447,-32.78882294852913,0.0,0.0,313,351,6666.666666666613,16666.666666666606,82623.69370804675,7693053.501499663,266621.00456621015,232146.07246972318 +1.8,1.2,0.6,0.05,1,1050733.055793884,-13.569701372539521,0.0,0.0,-13.569701372539521,0.0,0.0,325,349,6666.666666666613,16666.666666666606,15976.649492714316,3422155.860564356,266666.6666666668,165976.64949271438 +1.8,1.2,0.6,0.05,2,844686.0416215243,-15.607834423519893,0.0,0.0,-15.607834423519893,0.0,0.0,326,343,6666.666666666613,16666.666666666606,21887.62904489281,3875074.316337777,266666.66666666645,171887.62904489273 +1.8,1.2,0.6,0.05,3,1729241.8096784095,-19.799964509801157,0.0,0.0,-19.799964509801157,0.0,0.0,332,353,6666.666666666613,16666.666666666606,36475.21753227176,4806658.779955839,266666.6666666666,186475.2175322718 +1.8,1.2,0.6,0.05,4,518040.96651750687,-13.173868479390675,0.0,0.0,-13.173868479390675,0.0,0.0,321,355,6666.666666666613,16666.666666666606,14007.140389139919,3334192.995420162,266666.66666666645,164007.1403891399 +1.8,1.2,0.9,0.03,0,755656.4658109522,-36.682509450221225,0.00020465979310681603,0.0,-36.682591642002045,0.0,0.0,329,354,6666.666666666613,16490.7002617415,16732.01408382,8558335.433382537,266448.06280484423,166732.01408382013 +1.8,1.2,0.9,0.03,1,267691.98284759733,-27.03940285244553,0.0,0.0,-27.03940285244553,0.0,0.0,342,356,6666.666666666613,15066.528821721939,5808.616133468787,6415422.856099052,265066.5288217219,155808.61613346892 +1.8,1.2,0.9,0.03,2,563940.1693568268,-17.181376921356843,0.00037428470915334066,0.0,-17.181623496699313,0.0,0.0,335,352,6666.666666666613,11872.99639219917,534.7205154290341,4224750.426968217,261795.0204111258,150534.72051542898 +1.8,1.2,0.9,0.03,3,752543.1977629972,-20.72072052299018,0.0,0.0,-20.72072052299018,0.0,0.0,337,356,6666.666666666613,13425.421189988745,2134.7433225589843,5011271.227331186,263425.42118998885,152134.74332255902 +1.8,1.2,0.9,0.03,4,689197.6225002896,-10.846704925661635,0.0,0.0,-10.846704925661635,0.0,0.0,335,356,6664.628753846007,6007.361950916707,831.9668774985911,2817043.5011231056,256007.3619509168,150831.96687749858 +1.8,1.2,0.9,0.04,0,569626.642324486,-17.191959724256506,0.00019063152499963006,0.0,-17.19204191603733,0.0,0.0,340,352,6666.666666666613,13221.877296992816,5204.655570504403,4227102.1609459305,263182.1623959515,155204.65557050455 +1.8,1.2,0.9,0.04,1,146287.5610140359,-11.20088914311805,0.0,0.0,-11.200960234699645,0.0,0.0,341,358,6646.845277251247,9095.966144586042,835.2756082467888,2895733.321525714,259095.96614458607,150835.27560824683 +1.8,1.2,0.9,0.04,2,691454.4225896732,-26.608468702590876,0.0004383561643835616,0.00011078744641080486,-26.608633086152523,0.0,0.0,327,350,6666.666666666613,16663.16687163042,12637.045070842081,6319659.7116869,266571.84267071687,162620.4269538804 +1.8,1.2,0.9,0.04,3,1289407.0491165137,-28.43305052127944,0.0,0.0,-28.43305052127944,0.0,0.0,333,358,6666.666666666613,16484.631789008152,18062.808564604155,6725122.33806213,266484.63178900816,168062.80856460397 +1.8,1.2,0.9,0.04,4,427244.94023321033,-18.137493184397577,0.0,0.0,-18.137493184397577,0.0,0.0,330,346,6666.666666666613,13307.565362192747,8080.86878493544,4437220.707643946,263307.5653621931,158080.86878493548 +1.8,1.2,0.9,0.05,0,189006.18822089146,-12.720747642117814,0.00014838901147152058,0.0,-12.721158601021928,0.0,0.0,331,358,6666.666666666613,12733.87005242011,5109.185425042414,3233499.4760262035,262702.9556750305,155109.18542504244 +1.8,1.2,0.9,0.05,1,352314.4730448742,-18.22009925184253,0.0,0.0,-18.22009925184253,0.0,0.0,335,355,6666.666666666613,15397.415906642827,11592.568432349999,4455577.611520589,265397.41590664285,161592.56843235 +1.8,1.2,0.9,0.05,2,1074672.1288918555,-44.15464328516538,0.0,0.0,-44.15464328516538,0.0,0.0,332,357,6666.666666666613,16666.666666666606,55685.99279701898,10218809.618925653,266666.66666666634,205685.99279701881 +1.8,1.2,0.9,0.05,3,2136832.167754409,-32.33956182680449,0.0006575342465753425,0.003816927289476157,-32.33980840214696,0.0,0.0,336,351,6666.666666666613,16666.666666666606,42960.13574582104,7593235.961512147,266529.68036529655,192387.5966523997 +1.8,1.2,0.9,0.05,4,278615.5982329955,-14.033904033243662,0.0,0.0,-14.033904033243662,0.0,0.0,342,354,6666.666666666613,13320.332305281992,6576.680731415921,3525312.007387506,263320.3323052823,156576.68073141575 +1.8,1.35,0.3,0.03,0,1077295.0819960013,-8.089138148480128,0.00021917808219178083,0.0001833935877839031,-8.08922034026095,0.0,0.0,266,324,6666.666666666613,14814.814814814868,13625.415517638781,2204252.921884471,264774.2262810748,163597.906479471 +1.8,1.35,0.3,0.03,1,1050024.509802224,-6.765346031087812,0.00021917808219178083,0.0001758242873091227,-6.765428222868635,0.0,0.0,288,330,6666.666666666613,14814.814814814868,8768.506173890943,1910076.8957972887,264774.226281075,158742.13253079465 +1.8,1.35,0.3,0.03,2,869032.2126285807,-5.836023284678313,0.001315068493150685,0.0003946484355888337,-5.836516435363244,0.0,0.0,277,332,6666.666666666613,14814.814814814868,5390.279055914481,1703560.7299285112,264571.2836123788,155331.0817905762 +1.8,1.35,0.3,0.03,3,703607.6119709329,-5.601982315519746,0.0,0.0,-5.601982315519746,0.0,0.0,258,323,6666.666666666613,14814.335956718716,5708.111693717603,1651551.6256710533,264814.33595671767,155708.11169371763 +1.8,1.35,0.3,0.03,4,1048383.9029282152,-5.986479699348314,0.0,0.0,-5.986479699348314,0.0,0.0,271,329,6666.666666666613,14814.814814814868,6639.474413483702,1736995.4887440687,264814.8148148139,156639.47441348358 +1.8,1.35,0.3,0.04,0,736947.2884355545,-6.436191650352965,0.00021917808219178083,0.00013370064002197538,-6.4362738421337875,0.0,0.0,282,331,6666.666666666613,14814.814814814868,13703.238676583956,1836931.4778562123,264774.2262810748,163683.1835805805 +1.8,1.35,0.3,0.04,1,1160633.6878301548,-6.25298783368078,0.00021917808219178083,0.0004494002284700984,-6.253070025461601,0.0,0.0,249,323,6666.666666666613,14814.814814814868,19575.61995726799,1796219.518595729,264774.2262810748,169508.2099229975 +1.8,1.35,0.3,0.04,2,1371899.1143548866,-8.347078302549868,0.00021917808219178083,0.0005845300577459889,-8.347160494330689,0.0,0.0,272,325,6666.666666666613,14814.814814814868,29921.639756564662,2261572.9561221898,264774.22628107463,179833.9602479029 +1.8,1.35,0.3,0.04,3,1389428.8597461611,-7.169104004836862,0.0008767123287671233,0.002354217024420062,-7.16943277196015,0.0,0.0,257,332,6666.666666666613,14814.814814814868,25122.629608475912,1999800.8899637454,264652.4606798572,174769.4970548129 +1.8,1.35,0.3,0.04,4,959636.6328908887,-7.494214908036989,0.0,0.0,-7.494214908036989,0.0,0.0,279,325,6666.666666666613,14814.814814814868,24276.431064778953,2072047.7573415504,264814.81481481396,174276.43106477903 +1.8,1.35,0.3,0.05,0,987316.4040847546,-5.297438840343794,0.0,0.0,-5.297438840343794,0.0,0.0,274,335,6666.666666666613,14814.814814814868,22656.859379424055,1583875.2978541749,264814.8148148141,172656.8593794239 +1.8,1.35,0.3,0.05,1,1115393.5175371843,-6.414887265933262,0.00043835616438356166,0.001216949748332061,-6.415051649494906,0.0,0.0,259,325,6666.666666666613,14814.814814814868,29257.808925043242,1832197.170207391,264733.63774733554,179075.26646279328 +1.8,1.35,0.3,0.05,2,1555650.367547935,-7.377860169033493,0.00043835616438356166,0.0019893932044253828,-7.378024552595137,0.0,0.0,268,336,6666.666666666613,14814.814814814868,40139.27406599238,2046191.1486741083,264733.6377473358,189840.86508532855 +1.8,1.35,0.3,0.05,3,1619923.4913717816,-8.754829821823538,0.0006575342465753425,0.003064781970571578,-8.755076397166004,0.0,0.0,271,329,6666.666666666613,14814.814814814868,42731.97567478331,2352184.404849673,264693.04921359615,192272.25837919745 +1.8,1.35,0.3,0.05,4,1679319.058549261,-9.644393901082516,0.0,0.0,-9.644393901082516,0.0,0.0,259,316,6666.666666666613,14814.814814814868,50519.32280561523,2549865.3113516704,264814.8148148142,200519.32280561532 +1.8,1.35,0.6,0.03,0,955242.6371706581,-15.673039952004034,0.00043835616438356166,0.00024529616874355883,-15.673204335565678,0.0,0.0,313,345,6666.666666666613,14814.814814814868,7767.921754292888,3889564.4337786958,264733.6377473358,157731.1273289815 +1.8,1.35,0.6,0.03,1,587330.2845657218,-11.684995132860692,0.000474763204483776,0.0,-11.685241708203161,0.0,0.0,326,345,6666.666666666613,13094.565198472066,1715.3392338866363,3003332.2517468296,263006.6460865305,151715.33923388654 +1.8,1.35,0.6,0.03,2,1519369.60554616,-19.104895966957706,0.0,0.0,-19.104895966957706,0.0,0.0,311,342,6666.666666666613,14814.814814814868,14293.735417761596,4652199.103768401,264814.814814814,164293.73541776167 +1.8,1.35,0.6,0.03,3,351012.4268370521,-10.09407987298389,0.0,0.0,-10.09407987298389,0.0,0.0,318,349,6666.666666666613,12175.496868113014,818.8233617245971,2649795.5273297536,262175.4968681129,150818.82336172464 +1.8,1.35,0.6,0.03,4,503325.57484739675,-10.404161691532474,0.0003480182692502867,0.0,-10.404408266874944,0.0,0.0,316,349,6666.666666666613,12147.252384330606,938.5239823973633,2718702.598118334,262082.8045566913,150938.52398239743 +1.8,1.35,0.6,0.04,0,669731.1102016576,-13.7188467774374,0.00043835616438356166,0.00015833774172915838,-13.719011160999047,0.0,0.0,322,345,6666.666666666613,14647.999269001884,8941.612657969099,3455299.2838749904,264566.82220152294,158917.86199670975 +1.8,1.35,0.6,0.04,1,761170.0347950751,-21.610560176989843,0.00021917808219178083,0.00015183083483304478,-21.610642368770666,0.0,0.0,331,344,6666.666666666613,14814.814814814868,26636.57750033467,5209013.372664438,264774.2262810748,176613.80287510992 +1.8,1.35,0.6,0.04,2,1744038.973657655,-24.664312574173433,0.0,0.0,-24.664312574173433,0.0,0.0,320,347,6666.666666666613,14814.814814814868,36540.108874408186,5887625.016483014,264814.81481481425,186540.1088744083 +1.8,1.35,0.6,0.04,3,2536813.146579581,-21.341947882214416,0.0006575342465753425,0.0037681663458465457,-21.342194457556886,0.0,0.0,323,347,6666.666666666613,14814.814814814868,32258.811117260393,5149321.751603217,264693.04921359645,181693.58616538337 +1.8,1.35,0.6,0.04,4,578016.4639838756,-11.366203512730019,0.0008493082992896547,3.5567178709702933e-05,-11.366532279853311,0.0,0.0,322,346,6666.666666666613,14536.403034203215,6786.992318172011,2932489.6694955705,264379.1237195193,156781.65724136555 +1.8,1.35,0.6,0.05,0,485979.55025350663,-11.747870605235166,0.0006575342465753425,0.00011378633739042958,-11.748117180577633,0.0,0.0,323,339,6666.666666666613,14221.89845460292,13685.421650940318,3017304.5789411655,264100.13285338465,163668.35370033176 +1.8,1.35,0.6,0.05,1,1555875.4443079163,-13.19158370435521,0.0008767123287671233,0.0037624943148634217,-13.191912471478501,0.0,0.0,317,341,6666.666666666613,14814.814814814868,23773.970316901814,3338129.71207894,264652.46067985723,173209.59616967244 +1.8,1.35,0.6,0.05,2,1940817.420499776,-22.45017747416438,0.00021917808219178083,0.0013426998171960196,-22.4502596659452,0.0,0.0,327,348,6666.666666666613,14814.814814814868,48076.34575351809,5395594.994258774,264774.226281075,197874.94078093872 +1.8,1.35,0.6,0.05,3,618243.6957547516,-9.192978155925353,0.0006575342465753425,0.0004622822272580322,-9.193224731267819,0.0,0.0,325,347,6666.666666666613,14799.553316576748,8553.320810236288,2449550.7013167394,264677.78771535825,158483.9784761477 +1.8,1.35,0.6,0.05,4,960141.9729707815,-25.038293347132154,0.00043835616438356166,0.0009212093847096257,-25.0384577306938,0.0,0.0,312,347,6666.666666666613,14814.814814814868,49203.17715908641,5970731.854918285,264733.63774733554,199064.9957513797 +1.8,1.35,0.9,0.03,0,885245.2390115751,-15.624354373527861,0.0,0.0,-15.624354373527861,0.0,0.0,333,349,6666.666666666613,11360.177407430983,798.8111340385448,3878745.416339543,261360.17740743083,150798.8111340386 +1.8,1.35,0.9,0.03,1,1614488.2833603332,-32.59628047483969,0.0,0.0,-32.59628047483969,0.0,0.0,328,355,6666.666666666613,14814.814814814868,18261.856738174964,7650284.549964417,264814.81481481425,168261.85673817483 +1.8,1.35,0.9,0.03,2,1011742.2365259717,-51.426670790869736,0.0,0.0,-51.426670790869736,0.0,0.0,338,357,6666.666666666613,14814.814814814868,28651.207414578133,11834815.731304357,264814.8148148142,178651.20741457827 +1.8,1.35,0.9,0.03,3,613783.0613126727,-23.81336647550346,0.0005392168426950124,0.0,-23.81369524262675,0.0,0.0,338,351,6666.666666666613,13634.80762851058,5617.073956407347,5698525.883445254,263534.9526576408,155617.07395640746 +1.8,1.35,0.9,0.03,4,345599.6193389067,-26.931391313614988,5.293267585428918e-05,0.0,-26.931473505395807,0.0,0.0,336,356,6666.666666666613,12067.8337239205,9332.338549394137,6391420.291914479,262058.0313765398,159332.3385493941 +1.8,1.35,0.9,0.04,0,1870792.8835957048,-31.514126871085985,0.0,0.0,-31.514126871085985,0.0,0.0,334,357,6666.666666666613,14807.696590550595,24288.38307214108,7409805.971352473,264807.6965905503,174288.38307214103 +1.8,1.35,0.9,0.04,1,902471.1602247966,-24.833223215532506,0.0,0.0,-24.833223215532506,0.0,0.0,342,351,6666.666666666613,14812.039813538908,12390.064771918862,5925160.7145628175,264812.0398135381,162390.06477191884 +1.8,1.35,0.9,0.04,2,880643.3306406141,-26.463693545527676,0.0006575342465753425,0.0005680378298035892,-26.46394012087014,0.0,0.0,342,352,6666.666666666613,14744.351283056125,17853.930429218493,6287487.4545617355,264622.58568183787,167768.7247547478 +1.8,1.35,0.9,0.04,3,652972.193067558,-24.317723663404873,0.0,0.0,-24.317723663404873,0.0,0.0,332,354,6666.666666666613,14814.814814814868,13692.600366714998,5810605.258534464,264814.8148148141,163692.60036671508 +1.8,1.35,0.9,0.04,4,461173.6857281607,-22.150122046652545,0.0,0.0,-22.150122046652545,0.0,0.0,337,351,6666.666666666613,14736.540351698603,10363.343622214665,5328916.010367275,264736.5403516979,160363.3436222145 +1.8,1.35,0.9,0.05,0,263772.80382469174,-16.597879238026646,0.0,0.0,-16.597879238026646,0.0,0.0,332,352,6666.666666666613,14216.887478931772,8780.997029134242,4095084.275117068,264216.88747893146,158780.997029134 +1.8,1.35,0.9,0.05,1,747253.9720776794,-35.237552261354125,0.00021917808219178083,0.00025320535597780875,-35.23763445313495,0.0,0.0,340,356,6666.666666666613,14814.814814814868,39286.98042829617,8237233.835856525,264774.226281075,189248.9996248994 +1.8,1.35,0.9,0.05,2,134540.0933659414,-15.807757969929638,6.928604537522937e-07,0.0,-15.80784016171046,0.0,0.0,338,357,6649.641255418604,10044.548722825295,8764.877194824317,3919484.7456842633,260044.42041533353,158764.87719482448 +1.8,1.35,0.9,0.05,3,509700.5647556068,-27.198681373832716,0.0,0.0,-27.198681373832716,0.0,0.0,336,350,6666.666666666613,14814.814814814868,26475.955353361063,6450818.083073973,264814.81481481413,176475.95535336106 +1.8,1.35,0.9,0.05,4,560269.8344499855,-15.09801871267748,0.0,0.0,-15.09801871267748,0.0,0.0,342,357,6666.666666666613,14727.506693418181,8086.613999296813,3761781.9361505765,264727.5066934176,158086.61399929665 diff --git a/tests/test_cadcad_integration.py b/tests/test_cadcad_integration.py new file mode 100644 index 0000000..198da3a --- /dev/null +++ b/tests/test_cadcad_integration.py @@ -0,0 +1,385 @@ +"""Lightweight integration tests for cadCAD state management (src/cadcad/). + +These tests verify that the cadCAD state infrastructure (create_initial_state, +bootstrap_system, sync_metrics, extract_metrics) works correctly without +running full multi-step simulations. + +Note: src/cadcad/config.py imports pandas (not installed in test env), so +bootstrap_system logic is replicated inline here using only the underlying +primitives, which are the same code paths config.py calls. +""" + +import pytest +import numpy as np + +from src.cadcad.state import ( + MycoFiState, + create_initial_state, + extract_metrics, + sync_metrics, +) +from src.primitives.risk_tranching import ( + TrancheParams, deposit_collateral, mint_tranche, +) +from src.primitives.conviction import ( + ConvictionParams, Proposal, Voter, stake as cv_stake, +) +from src.crosschain.hub_spoke import simulate_deposit + + +# ---------- Inline bootstrap helper (mirrors config.bootstrap_system) ---------- + +def bootstrap_state( + state: dict, + initial_deposits: dict | None = None, + initial_tranche_mints: dict | None = None, + n_voters: int = 5, +) -> dict: + """Replicate bootstrap_system logic without importing config.py (pandas).""" + s: MycoFiState = state["mycofi"] + + if initial_deposits and s.crosschain: + total_usd = 0.0 + for chain, assets in initial_deposits.items(): + for asset_sym, qty in assets.items(): + simulate_deposit(s.crosschain, chain, asset_sym, qty, 0.0) + spoke = s.crosschain.hub.spokes[chain] + for a in spoke.accepted_assets: + if a.symbol == asset_sym: + total_usd += qty * a.price + break + s.crosschain.hub.process_messages(0.0) + if s.tranche_system: + deposit_collateral(s.tranche_system, total_usd) + + if initial_tranche_mints and s.tranche_system: + for tranche, amount in initial_tranche_mints.items(): + mint_tranche(s.tranche_system, tranche, amount) + + if s.myco_system and s.crosschain: + total_value = s.crosschain.hub.total_collateral_usd + if total_value > 0: + n = s.myco_system.config.n_reserve_assets + amounts = np.full(n, total_value / n) + s.myco_system.deposit(amounts, 0.0) + + if s.governance: + for i in range(n_voters): + voter_id = f"voter_{i}" + holdings = float(np.random.lognormal(mean=np.log(5000), sigma=1.0)) + s.governance.voters[voter_id] = Voter( + id=voter_id, + holdings=holdings, + sentiment=float(np.random.uniform(0.3, 0.9)), + ) + s.governance.total_supply = sum( + v.holdings for v in s.governance.voters.values() + ) + + sync_metrics(s) + return state + + +# ---------- create_initial_state ---------- + +class TestCreateInitialState: + def test_returns_dict_with_mycofi_key(self): + state = create_initial_state() + assert "mycofi" in state + + def test_mycofi_is_mycofi_state(self): + state = create_initial_state() + assert isinstance(state["mycofi"], MycoFiState) + + def test_myco_system_is_populated(self): + state = create_initial_state() + assert state["mycofi"].myco_system is not None + + def test_tranche_system_is_populated(self): + state = create_initial_state() + assert state["mycofi"].tranche_system is not None + + def test_crosschain_is_populated(self): + state = create_initial_state() + assert state["mycofi"].crosschain is not None + + def test_governance_is_populated(self): + state = create_initial_state() + assert state["mycofi"].governance is not None + + def test_crosschain_has_five_chains(self): + state = create_initial_state() + s = state["mycofi"] + assert len(s.crosschain.hub.spokes) == 5 + + def test_total_chains_reflects_crosschain(self): + state = create_initial_state() + s = state["mycofi"] + assert s.total_chains == len(s.crosschain.hub.spokes) + + def test_custom_tranche_params(self): + tp = TrancheParams(senior_collateral_ratio=2.0) + state = create_initial_state(tranche_params=tp) + assert state["mycofi"].tranche_system.params.senior_collateral_ratio == 2.0 + + def test_custom_conviction_params(self): + cp = ConvictionParams(alpha=0.8, beta=0.3) + state = create_initial_state(conviction_params=cp) + assert state["mycofi"].governance.params.alpha == 0.8 + assert state["mycofi"].governance.params.beta == 0.3 + + def test_initial_aggregate_fields_are_zero(self): + state = create_initial_state() + s = state["mycofi"] + assert s.time == 0.0 + assert s.total_collateral_usd == 0.0 + + +# ---------- bootstrap_system (inline implementation) ---------- + +class TestBootstrapSystem: + def test_returns_dict_with_mycofi_key(self): + state = create_initial_state() + result = bootstrap_state(state) + assert "mycofi" in result + + def test_populates_governance_voters(self): + state = create_initial_state() + bootstrap_state(state, n_voters=10) + assert len(state["mycofi"].governance.voters) == 10 + + def test_governance_voters_have_holdings(self): + state = create_initial_state() + bootstrap_state(state, n_voters=5) + for voter in state["mycofi"].governance.voters.values(): + assert voter.holdings > 0.0 + + def test_governance_total_supply_updated(self): + state = create_initial_state() + bootstrap_state(state, n_voters=5) + s = state["mycofi"] + expected_supply = sum(v.holdings for v in s.governance.voters.values()) + assert s.governance.total_supply == pytest.approx(expected_supply) + + def test_with_initial_deposits_seeds_crosschain(self): + state = create_initial_state() + deposits = {"ethereum": {"stETH": 50.0}} + bootstrap_state(state, initial_deposits=deposits, n_voters=3) + spoke = state["mycofi"].crosschain.hub.spokes["ethereum"] + assert spoke.balances.get("stETH", 0.0) == pytest.approx(50.0) + + def test_with_initial_deposits_seeds_tranche_collateral(self): + state = create_initial_state() + deposits = {"ethereum": {"stETH": 100.0}} + bootstrap_state(state, initial_deposits=deposits, n_voters=3) + s = state["mycofi"] + assert s.tranche_system.total_collateral > 0.0 + + def test_with_initial_tranche_mints(self): + state = create_initial_state() + deposits = {"ethereum": {"stETH": 1000.0}} + mints = {"senior": 100_000.0, "mezzanine": 50_000.0} + bootstrap_state(state, initial_deposits=deposits, + initial_tranche_mints=mints, n_voters=3) + s = state["mycofi"] + # Verify no crash and state is internally consistent + assert s.tranche_system.senior.supply >= 0.0 + assert s.tranche_system.mezzanine.supply >= 0.0 + + def test_default_bootstrap_no_crash(self): + state = create_initial_state() + bootstrap_state(state) # Should not raise + assert "mycofi" in state + + def test_sync_metrics_called_by_bootstrap(self): + state = create_initial_state() + deposits = {"ethereum": {"stETH": 100.0}} + bootstrap_state(state, initial_deposits=deposits, n_voters=5) + s = state["mycofi"] + assert s.total_chains == 5 + + +# ---------- sync_metrics ---------- + +class TestSyncMetrics: + def _make_bootstrapped_state(self) -> MycoFiState: + state = create_initial_state() + deposits = {"ethereum": {"stETH": 100.0}, "base": {"cbETH": 50.0}} + mints = {"senior": 50_000.0, "mezzanine": 20_000.0, "junior": 10_000.0} + bootstrap_state(state, initial_deposits=deposits, + initial_tranche_mints=mints, n_voters=5) + return state["mycofi"] + + def test_sync_returns_state(self): + s = self._make_bootstrapped_state() + result = sync_metrics(s) + assert result is s + + def test_sync_updates_senior_supply(self): + s = self._make_bootstrapped_state() + s.tranche_system.senior.supply = 999.0 + sync_metrics(s) + assert s.senior_supply == pytest.approx(999.0) + + def test_sync_updates_mezzanine_supply(self): + s = self._make_bootstrapped_state() + s.tranche_system.mezzanine.supply = 888.0 + sync_metrics(s) + assert s.mezzanine_supply == pytest.approx(888.0) + + def test_sync_updates_junior_supply(self): + s = self._make_bootstrapped_state() + s.tranche_system.junior.supply = 777.0 + sync_metrics(s) + assert s.junior_supply == pytest.approx(777.0) + + def test_sync_updates_collateral_ratios(self): + s = self._make_bootstrapped_state() + sync_metrics(s) + assert s.senior_cr == pytest.approx(s.tranche_system.senior.collateral_ratio) + assert s.mezzanine_cr == pytest.approx(s.tranche_system.mezzanine.collateral_ratio) + + def test_sync_updates_system_collateral_ratio(self): + s = self._make_bootstrapped_state() + sync_metrics(s) + assert s.system_collateral_ratio == pytest.approx( + s.tranche_system.system_collateral_ratio + ) + + def test_sync_updates_total_chains(self): + s = self._make_bootstrapped_state() + sync_metrics(s) + assert s.total_chains == len(s.crosschain.hub.spokes) + + def test_sync_updates_total_collateral_usd(self): + s = self._make_bootstrapped_state() + sync_metrics(s) + assert s.total_collateral_usd == pytest.approx(s.crosschain.hub.total_collateral_usd) + + def test_sync_updates_governance_epoch(self): + s = self._make_bootstrapped_state() + s.governance.epoch = 42 + sync_metrics(s) + assert s.governance_epoch == 42 + + def test_sync_updates_proposals_passed(self): + s = self._make_bootstrapped_state() + dummy = Proposal(id="x", title="done", status="passed") + s.governance.passed_proposals.append(dummy) + sync_metrics(s) + assert s.proposals_passed == 1 + + def test_sync_updates_total_staked(self): + s = self._make_bootstrapped_state() + prop = Proposal(id="p1", title="Test", funds_requested=0.01) + s.governance.proposals["p1"] = prop + v = list(s.governance.voters.values())[0] + cv_stake(s.governance, v.id, "p1", min(100.0, v.holdings)) + sync_metrics(s) + assert s.total_staked > 0.0 + + def test_sync_updates_total_yield(self): + s = self._make_bootstrapped_state() + s.crosschain.total_yield_generated = 999.99 + sync_metrics(s) + assert s.total_yield == pytest.approx(999.99) + + def test_sync_with_none_subsystems_is_safe(self): + s = MycoFiState() + sync_metrics(s) # Should not raise + + +# ---------- extract_metrics ---------- + +class TestExtractMetrics: + def _make_synced_state(self) -> MycoFiState: + state = create_initial_state() + deposits = {"ethereum": {"stETH": 100.0}} + bootstrap_state(state, initial_deposits=deposits, n_voters=5) + s = state["mycofi"] + sync_metrics(s) + return s + + def test_returns_dict(self): + s = self._make_synced_state() + m = extract_metrics(s) + assert isinstance(m, dict) + + def test_contains_time(self): + s = self._make_synced_state() + m = extract_metrics(s) + assert "time" in m + + def test_contains_total_supply(self): + s = self._make_synced_state() + m = extract_metrics(s) + assert "total_supply" in m + + def test_contains_total_collateral_usd(self): + s = self._make_synced_state() + m = extract_metrics(s) + assert "total_collateral_usd" in m + + def test_contains_system_cr(self): + s = self._make_synced_state() + m = extract_metrics(s) + assert "system_cr" in m + + def test_contains_myco_price(self): + s = self._make_synced_state() + m = extract_metrics(s) + assert "myco_price" in m + + def test_contains_tranche_fields(self): + s = self._make_synced_state() + m = extract_metrics(s) + for field_name in ["senior_supply", "senior_cr", "mezzanine_supply", + "mezzanine_cr", "junior_supply", "junior_cr"]: + assert field_name in m, f"Missing field: {field_name}" + + def test_contains_crosschain_fields(self): + s = self._make_synced_state() + m = extract_metrics(s) + assert "total_chains" in m + assert "total_yield" in m + assert "ccip_messages" in m + + def test_contains_governance_fields(self): + s = self._make_synced_state() + m = extract_metrics(s) + assert "governance_epoch" in m + assert "total_staked" in m + assert "proposals_passed" in m + + def test_per_chain_collateral_included(self): + s = self._make_synced_state() + m = extract_metrics(s) + for chain in ["ethereum", "arbitrum", "optimism", "base", "polygon"]: + assert f"collateral_{chain}" in m, f"Missing per-chain field: collateral_{chain}" + + def test_values_match_state(self): + s = self._make_synced_state() + m = extract_metrics(s) + assert m["senior_supply"] == pytest.approx(s.senior_supply) + assert m["mezzanine_supply"] == pytest.approx(s.mezzanine_supply) + assert m["junior_supply"] == pytest.approx(s.junior_supply) + assert m["governance_epoch"] == s.governance_epoch + assert m["total_chains"] == s.total_chains + + def test_per_chain_values_match_spokes(self): + s = self._make_synced_state() + m = extract_metrics(s) + for chain, spoke in s.crosschain.hub.spokes.items(): + assert m[f"collateral_{chain}"] == pytest.approx(spoke.total_value_usd) + + def test_all_values_numeric(self): + state = create_initial_state() + deposits = {"ethereum": {"stETH": 500.0}} + mints = {"senior": 100_000.0, "mezzanine": 50_000.0, "junior": 20_000.0} + bootstrap_state(state, initial_deposits=deposits, + initial_tranche_mints=mints, n_voters=3) + s = state["mycofi"] + sync_metrics(s) + m = extract_metrics(s) + for key, value in m.items(): + assert isinstance(value, (int, float)), f"Non-numeric metric: {key}={value}" diff --git a/tests/test_conviction.py b/tests/test_conviction.py new file mode 100644 index 0000000..75e6534 --- /dev/null +++ b/tests/test_conviction.py @@ -0,0 +1,472 @@ +"""Tests for conviction voting governance (src/primitives/conviction.py).""" + +import math +import numpy as np +import pytest + +from src.primitives.conviction import ( + ConvictionParams, + Proposal, + Voter, + ConvictionSystem, + trigger_threshold, + update_conviction, + max_conviction, + conviction_at_time, + epochs_to_fraction, + stake, + unstake, + tick, + generate_conviction_curves, + get_governance_metrics, +) + + +# ---------- Helpers ---------- + +def make_system(n_voters: int = 3, holdings_each: float = 10_000.0) -> ConvictionSystem: + """Return a ConvictionSystem with a few voters.""" + params = ConvictionParams(alpha=0.9, beta=0.2, rho=0.0025, min_age=3) + system = ConvictionSystem( + params=params, + total_supply=n_voters * holdings_each, + total_funds=50_000.0, + ) + for i in range(n_voters): + system.voters[f"voter_{i}"] = Voter( + id=f"voter_{i}", + holdings=holdings_each, + sentiment=0.7, + ) + return system + + +def add_proposal( + system: ConvictionSystem, + prop_id: str = "p1", + funds_requested: float = 0.05, +) -> Proposal: + prop = Proposal( + id=prop_id, + title="Test Proposal", + funds_requested=funds_requested, + ) + system.proposals[prop_id] = prop + return prop + + +# ---------- ConvictionParams ---------- + +class TestConvictionParams: + def test_defaults(self): + p = ConvictionParams() + assert p.alpha == 0.9 + assert p.beta == 0.2 + assert p.rho == 0.0025 + assert p.min_age == 3 + + def test_half_life_formula(self): + p = ConvictionParams(alpha=0.5) + # T_half = -ln(2)/ln(0.5) = 1.0 + assert p.half_life == pytest.approx(1.0) + + def test_half_life_alpha_09(self): + p = ConvictionParams(alpha=0.9) + expected = -np.log(2) / np.log(0.9) + assert p.half_life == pytest.approx(expected) + + def test_half_life_alpha_zero_is_inf(self): + p = ConvictionParams(alpha=0.0) + assert p.half_life == float("inf") + + def test_from_half_life_roundtrip(self): + target_hl = 10.0 + p = ConvictionParams.from_half_life(target_hl) + assert p.half_life == pytest.approx(target_hl, rel=1e-6) + + def test_from_half_life_kwargs_forwarded(self): + p = ConvictionParams.from_half_life(10.0, beta=0.3, min_age=5) + assert p.beta == 0.3 + assert p.min_age == 5 + + +# ---------- update_conviction ---------- + +class TestUpdateConviction: + def test_formula_y_t_plus_1(self): + alpha = 0.9 + y_t = 100.0 + x = 50.0 + result = update_conviction(y_t, x, alpha) + assert result == pytest.approx(alpha * y_t + x) + + def test_zero_initial_conviction(self): + result = update_conviction(0.0, 100.0, 0.9) + assert result == pytest.approx(100.0) + + def test_zero_staked(self): + result = update_conviction(200.0, 0.0, 0.9) + assert result == pytest.approx(0.9 * 200.0) + + def test_convergence_behavior(self): + # Repeatedly applying with same stake should converge to max_conviction + x = 100.0 + alpha = 0.9 + y = 0.0 + for _ in range(1000): + y = update_conviction(y, x, alpha) + assert y == pytest.approx(max_conviction(x, alpha), rel=1e-4) + + +# ---------- max_conviction ---------- + +class TestMaxConviction: + def test_formula(self): + x = 100.0 + alpha = 0.9 + assert max_conviction(x, alpha) == pytest.approx(x / (1 - alpha)) + + def test_alpha_one_is_inf(self): + assert max_conviction(100.0, 1.0) == float("inf") + + def test_higher_alpha_higher_max(self): + x = 100.0 + assert max_conviction(x, 0.95) > max_conviction(x, 0.5) + + +# ---------- conviction_at_time ---------- + +class TestConvictionAtTime: + def test_epoch_zero_equals_initial(self): + result = conviction_at_time(100.0, 0.9, 0, initial_conviction=50.0) + assert result == pytest.approx(50.0) + + def test_epoch_one_matches_update_formula(self): + x = 100.0 + alpha = 0.9 + y0 = 50.0 + result = conviction_at_time(x, alpha, 1, y0) + expected = update_conviction(y0, x, alpha) + assert result == pytest.approx(expected) + + def test_matches_iterative_application(self): + x = 75.0 + alpha = 0.85 + y0 = 0.0 + n = 20 + # Iterate manually + y = y0 + for _ in range(n): + y = update_conviction(y, x, alpha) + assert conviction_at_time(x, alpha, n, y0) == pytest.approx(y, rel=1e-9) + + def test_approaches_max_conviction(self): + x = 100.0 + alpha = 0.9 + y_large = conviction_at_time(x, alpha, 500) + assert y_large == pytest.approx(max_conviction(x, alpha), rel=1e-3) + + +# ---------- epochs_to_fraction ---------- + +class TestEpochsToFraction: + def test_half_is_half_life(self): + alpha = 0.9 + p = ConvictionParams(alpha=alpha) + t_half = epochs_to_fraction(0.5, alpha) + assert t_half == pytest.approx(p.half_life, rel=1e-6) + + def test_fraction_zero_is_inf(self): + assert epochs_to_fraction(0.0, 0.9) == float("inf") + + def test_fraction_one_is_inf(self): + assert epochs_to_fraction(1.0, 0.9) == float("inf") + + def test_higher_fraction_takes_longer(self): + alpha = 0.9 + t_50 = epochs_to_fraction(0.5, alpha) + t_90 = epochs_to_fraction(0.9, alpha) + assert t_90 > t_50 + + +# ---------- trigger_threshold ---------- + +class TestTriggerThreshold: + def test_formula(self): + p = ConvictionParams(alpha=0.9, beta=0.2, rho=0.0025) + supply = 100_000.0 + share = 0.05 + expected = p.rho * supply / ((1 - p.alpha) * (p.beta - share) ** 2) + assert trigger_threshold(share, supply, p) == pytest.approx(expected) + + def test_share_at_beta_returns_inf(self): + p = ConvictionParams(beta=0.2) + result = trigger_threshold(0.2, 100_000.0, p) + assert result == float("inf") + + def test_share_above_beta_returns_inf(self): + p = ConvictionParams(beta=0.2) + result = trigger_threshold(0.25, 100_000.0, p) + assert result == float("inf") + + def test_larger_supply_higher_threshold(self): + p = ConvictionParams() + t1 = trigger_threshold(0.05, 100_000.0, p) + t2 = trigger_threshold(0.05, 200_000.0, p) + assert t2 > t1 + + def test_smaller_share_lower_threshold(self): + p = ConvictionParams(beta=0.2) + t_small = trigger_threshold(0.01, 100_000.0, p) + t_large = trigger_threshold(0.1, 100_000.0, p) + assert t_small < t_large + + +# ---------- Voter stake/unstake ---------- + +class TestVoterStaking: + def test_stake_adds_to_voter_stakes(self): + system = make_system() + add_proposal(system, "p1", 0.05) + stake(system, "voter_0", "p1", 500.0) + assert system.voters["voter_0"].stakes.get("p1", 0.0) == pytest.approx(500.0) + + def test_stake_capped_at_holdings(self): + system = make_system(holdings_each=1_000.0) + add_proposal(system, "p1") + stake(system, "voter_0", "p1", 999_999.0) + assert system.voters["voter_0"].stakes.get("p1", 0.0) <= 1_000.0 + + def test_stake_on_unknown_voter_is_noop(self): + system = make_system() + add_proposal(system, "p1") + result = stake(system, "nobody", "p1", 100.0) + assert result is system # No crash, no change + + def test_stake_on_unknown_proposal_is_noop(self): + system = make_system() + result = stake(system, "voter_0", "nonexistent", 100.0) + assert result is system + + def test_unstake_removes_tokens(self): + system = make_system() + add_proposal(system, "p1") + stake(system, "voter_0", "p1", 500.0) + unstake(system, "voter_0", "p1", 200.0) + assert system.voters["voter_0"].stakes.get("p1", 0.0) == pytest.approx(300.0) + + def test_unstake_all_removes_key(self): + system = make_system() + add_proposal(system, "p1") + stake(system, "voter_0", "p1", 500.0) + unstake(system, "voter_0", "p1", 500.0) + assert "p1" not in system.voters["voter_0"].stakes + + def test_unstake_capped_at_staked(self): + system = make_system() + add_proposal(system, "p1") + stake(system, "voter_0", "p1", 200.0) + unstake(system, "voter_0", "p1", 999_999.0) + assert system.voters["voter_0"].stakes.get("p1", 0.0) == pytest.approx(0.0) + + def test_stake_accumulates_on_multiple_calls(self): + system = make_system(holdings_each=10_000.0) + add_proposal(system, "p1") + stake(system, "voter_0", "p1", 200.0) + stake(system, "voter_0", "p1", 300.0) + assert system.voters["voter_0"].stakes.get("p1", 0.0) == pytest.approx(500.0) + + def test_cannot_stake_more_than_available_across_proposals(self): + system = make_system(holdings_each=1_000.0) + add_proposal(system, "p1") + add_proposal(system, "p2") + stake(system, "voter_0", "p1", 800.0) + stake(system, "voter_0", "p2", 500.0) # Only 200 left + assert system.voters["voter_0"].stakes.get("p2", 0.0) == pytest.approx(200.0) + + +# ---------- tick ---------- + +class TestTick: + def test_tick_increments_epoch(self): + system = make_system() + tick(system) + assert system.epoch == 1 + + def test_tick_updates_conviction(self): + system = make_system() + add_proposal(system, "p1") + stake(system, "voter_0", "p1", 500.0) + tick(system) + assert system.voters["voter_0"].convictions.get("p1", 0.0) > 0.0 + + def test_tick_increments_proposal_age(self): + system = make_system() + add_proposal(system, "p1") + tick(system) + assert system.proposals["p1"].age == 1 + + def test_proposal_does_not_pass_before_min_age(self): + system = make_system(n_voters=5, holdings_each=100_000.0) + add_proposal(system, "p1", funds_requested=0.001) + # Stake heavily + for vid in system.voters: + stake(system, vid, "p1", 90_000.0) + # Tick fewer times than min_age (=3) + tick(system) + tick(system) + assert system.proposals["p1"].status == "candidate" + + def test_proposal_passes_after_sufficient_conviction(self): + """Create conditions where a proposal accumulates enough conviction.""" + params = ConvictionParams(alpha=0.9, beta=0.5, rho=0.0001, min_age=1) + system = ConvictionSystem( + params=params, + total_supply=1_000_000.0, + total_funds=100_000.0, + ) + # One whale voter with enormous holdings + system.voters["whale"] = Voter(id="whale", holdings=1_000_000.0) + prop = Proposal(id="p1", title="Easy Pass", funds_requested=0.001) + system.proposals["p1"] = prop + + # Stake all holdings + stake(system, "whale", "p1", 1_000_000.0) + + # Tick many times to build conviction far above threshold + for _ in range(100): + if system.proposals["p1"].status == "passed": + break + tick(system) + + assert system.proposals["p1"].status == "passed" + assert len(system.passed_proposals) == 1 + + +# ---------- generate_conviction_curves ---------- + +class TestGenerateConvictionCurves: + def test_returns_correct_keys(self): + curves = generate_conviction_curves(100.0, 0.9, epochs=50) + assert "time" in curves + assert "charge" in curves + assert "discharge" in curves + assert "max" in curves + + def test_correct_shape(self): + epochs = 30 + curves = generate_conviction_curves(100.0, 0.9, epochs=epochs) + assert len(curves["time"]) == epochs + assert len(curves["charge"]) == epochs + assert len(curves["discharge"]) == epochs + assert len(curves["max"]) == epochs + + def test_charge_starts_near_zero(self): + curves = generate_conviction_curves(100.0, 0.9, epochs=50) + # At t=0, charge = x*(1 - alpha^0)/(1-alpha) = x*0/(1-alpha) = 0 + assert curves["charge"][0] == pytest.approx(0.0) + + def test_charge_approaches_max(self): + curves = generate_conviction_curves(100.0, 0.9, epochs=500) + y_max = max_conviction(100.0, 0.9) + assert curves["charge"][-1] == pytest.approx(y_max, rel=1e-3) + + def test_discharge_starts_at_max(self): + curves = generate_conviction_curves(100.0, 0.9, epochs=50) + y_max = max_conviction(100.0, 0.9) + assert curves["discharge"][0] == pytest.approx(y_max) + + def test_discharge_decays(self): + curves = generate_conviction_curves(100.0, 0.9, epochs=50) + assert curves["discharge"][-1] < curves["discharge"][0] + + def test_max_array_constant(self): + curves = generate_conviction_curves(100.0, 0.9, epochs=20) + assert np.all(curves["max"] == curves["max"][0]) + + +# ---------- get_governance_metrics ---------- + +class TestGetGovernanceMetrics: + def test_returns_expected_fields(self): + system = make_system() + m = get_governance_metrics(system) + assert "epoch" in m + assert "total_supply" in m + assert "total_staked" in m + assert "staking_ratio" in m + assert "active_proposals" in m + assert "passed_proposals" in m + assert "total_voters" in m + assert "proposals" in m + + def test_voter_count_correct(self): + system = make_system(n_voters=4) + m = get_governance_metrics(system) + assert m["total_voters"] == 4 + + def test_staking_ratio_zero_when_nothing_staked(self): + system = make_system() + m = get_governance_metrics(system) + assert m["staking_ratio"] == pytest.approx(0.0) + + def test_staking_ratio_updates_after_stake(self): + system = make_system(n_voters=1, holdings_each=10_000.0) + add_proposal(system, "p1") + stake(system, "voter_0", "p1", 5_000.0) + m = get_governance_metrics(system) + assert m["staking_ratio"] == pytest.approx(5_000.0 / 10_000.0) + + def test_active_proposals_count(self): + system = make_system() + add_proposal(system, "p1") + add_proposal(system, "p2") + m = get_governance_metrics(system) + assert m["active_proposals"] == 2 + + def test_passed_proposals_count(self): + system = make_system() + dummy_proposal = Proposal(id="done", title="Done", status="passed") + system.passed_proposals.append(dummy_proposal) + m = get_governance_metrics(system) + assert m["passed_proposals"] == 1 + + +# ---------- Full governance lifecycle ---------- + +class TestFullGovernanceLifecycle: + def test_lifecycle(self): + """Create proposals, stake, tick until passage.""" + params = ConvictionParams(alpha=0.9, beta=0.5, rho=0.0001, min_age=2) + system = ConvictionSystem( + params=params, + total_supply=500_000.0, + total_funds=100_000.0, + ) + # Create voters + for i in range(5): + system.voters[f"v{i}"] = Voter(id=f"v{i}", holdings=100_000.0) + + # Create a low-threshold proposal + prop = Proposal(id="easy", title="Easy", funds_requested=0.001) + system.proposals["easy"] = prop + + # All voters stake + for i in range(5): + stake(system, f"v{i}", "easy", 80_000.0) + + # Tick until passed or timeout + passed = False + for _ in range(200): + tick(system) + if system.proposals["easy"].status == "passed": + passed = True + break + + assert passed + assert len(system.passed_proposals) == 1 + + # Verify metrics reflect state + m = get_governance_metrics(system) + assert m["passed_proposals"] == 1 + assert m["epoch"] > 0 diff --git a/tests/test_crosschain.py b/tests/test_crosschain.py new file mode 100644 index 0000000..43b2c29 --- /dev/null +++ b/tests/test_crosschain.py @@ -0,0 +1,505 @@ +"""Tests for hub-and-spoke cross-chain architecture (src/crosschain/hub_spoke.py).""" + +import pytest +import numpy as np + +from src.crosschain.hub_spoke import ( + StakingAsset, + CCIPMessage, + SpokeVault, + HubRegistry, + CrossChainSystem, + create_default_system, + simulate_deposit, + tick, + apply_price_shock, + get_crosschain_metrics, +) + + +# ---------- Helpers ---------- + +def make_eth_asset(price: float = 2400.0) -> StakingAsset: + return StakingAsset( + symbol="stETH", + chain="ethereum", + price=price, + staking_apy=0.035, + weight=1.0, + risk_score=0.05, + ) + + +def make_spoke(chain: str = "ethereum") -> SpokeVault: + asset = make_eth_asset() + return SpokeVault(chain=chain, accepted_assets=[asset]) + + +def make_hub_with_spoke() -> tuple[HubRegistry, SpokeVault]: + hub = HubRegistry() + spoke = make_spoke("ethereum") + hub.register_spoke(spoke) + return hub, spoke + + +# ---------- StakingAsset ---------- + +class TestStakingAsset: + def test_defaults(self): + asset = StakingAsset(symbol="stETH", chain="ethereum") + assert asset.price == 1.0 + assert asset.staking_apy == 0.04 + assert asset.weight == 1.0 + assert asset.risk_score == 0.1 + + def test_custom_values(self): + asset = StakingAsset("rETH", "arbitrum", price=2420.0, staking_apy=0.032) + assert asset.price == 2420.0 + assert asset.staking_apy == 0.032 + + +# ---------- create_default_system ---------- + +class TestCreateDefaultSystem: + def test_has_five_chains(self): + system = create_default_system() + assert len(system.hub.spokes) == 5 + + def test_expected_chains_present(self): + system = create_default_system() + chains = set(system.hub.spokes.keys()) + assert "ethereum" in chains + assert "arbitrum" in chains + assert "optimism" in chains + assert "base" in chains + assert "polygon" in chains + + def test_each_spoke_has_assets(self): + system = create_default_system() + for chain, spoke in system.hub.spokes.items(): + assert len(spoke.accepted_assets) > 0, f"{chain} has no assets" + + def test_initial_state_zeros(self): + system = create_default_system() + assert system.time == 0.0 + assert system.total_messages_sent == 0 + assert system.total_messages_delivered == 0 + assert system.total_yield_generated == 0.0 + assert system.hub.total_collateral_usd == 0.0 + + +# ---------- SpokeVault.deposit ---------- + +class TestSpokeVaultDeposit: + def test_deposit_increases_balance(self): + spoke = make_spoke() + spoke.deposit("stETH", 10.0, 0.0) + assert spoke.balances.get("stETH", 0.0) == pytest.approx(10.0) + + def test_deposit_returns_ccip_message(self): + spoke = make_spoke() + msg = spoke.deposit("stETH", 10.0, 0.0) + assert isinstance(msg, CCIPMessage) + + def test_deposit_message_type(self): + spoke = make_spoke() + msg = spoke.deposit("stETH", 5.0, 1.0) + assert msg.msg_type == "deposit_report" + + def test_deposit_message_source_chain(self): + spoke = make_spoke("ethereum") + msg = spoke.deposit("stETH", 5.0, 0.0) + assert msg.source_chain == "ethereum" + + def test_deposit_message_dest_chain(self): + spoke = make_spoke() + msg = spoke.deposit("stETH", 5.0, 0.0) + assert msg.dest_chain == "base" + + def test_deposit_message_payload(self): + spoke = make_spoke() + msg = spoke.deposit("stETH", 7.0, 0.0) + assert msg.payload["asset"] == "stETH" + assert msg.payload["amount"] == pytest.approx(7.0) + + def test_deposit_appends_to_pending_reports(self): + spoke = make_spoke() + assert len(spoke.pending_reports) == 0 + spoke.deposit("stETH", 5.0, 0.0) + assert len(spoke.pending_reports) == 1 + + def test_multiple_deposits_accumulate(self): + spoke = make_spoke() + spoke.deposit("stETH", 3.0, 0.0) + spoke.deposit("stETH", 7.0, 1.0) + assert spoke.balances["stETH"] == pytest.approx(10.0) + + def test_deposit_updates_total_value(self): + spoke = make_spoke() + price = spoke.accepted_assets[0].price + spoke.deposit("stETH", 5.0, 0.0) + assert spoke.total_value_usd == pytest.approx(5.0 * price * 1.0) + + +# ---------- SpokeVault.withdraw ---------- + +class TestSpokeVaultWithdraw: + def test_withdraw_decreases_balance(self): + spoke = make_spoke() + spoke.deposit("stETH", 10.0, 0.0) + spoke.withdraw("stETH", 3.0, 1.0) + assert spoke.balances["stETH"] == pytest.approx(7.0) + + def test_withdraw_creates_negative_report(self): + spoke = make_spoke() + spoke.deposit("stETH", 10.0, 0.0) + # Clear pending from deposit + spoke.pending_reports.clear() + msg = spoke.withdraw("stETH", 3.0, 1.0) + assert msg is not None + assert msg.payload["amount"] == pytest.approx(-3.0) + + def test_withdraw_capped_at_balance(self): + spoke = make_spoke() + spoke.deposit("stETH", 5.0, 0.0) + spoke.withdraw("stETH", 999.0, 1.0) + assert spoke.balances.get("stETH", 0.0) == pytest.approx(0.0) + + def test_withdraw_with_no_balance_returns_none(self): + spoke = make_spoke() + result = spoke.withdraw("stETH", 10.0, 0.0) + assert result is None + + def test_withdraw_appends_pending_report(self): + spoke = make_spoke() + spoke.deposit("stETH", 10.0, 0.0) + initial_count = len(spoke.pending_reports) + spoke.withdraw("stETH", 3.0, 1.0) + assert len(spoke.pending_reports) == initial_count + 1 + + +# ---------- SpokeVault.apply_staking_yield ---------- + +class TestSpokeVaultApplyStakingYield: + def test_yield_increases_balance(self): + spoke = make_spoke() + spoke.deposit("stETH", 100.0, 0.0) + balance_before = spoke.balances["stETH"] + spoke.apply_staking_yield(1.0 / 365) + assert spoke.balances["stETH"] > balance_before + + def test_yield_amount_correct(self): + spoke = make_spoke() + spoke.deposit("stETH", 100.0, 0.0) + asset = spoke.accepted_assets[0] + dt = 1.0 / 365 + expected_token_yield = 100.0 * asset.staking_apy * dt + expected_usd_yield = expected_token_yield * asset.price + actual_yield = spoke.apply_staking_yield(dt) + assert actual_yield == pytest.approx(expected_usd_yield) + + def test_empty_vault_yields_zero(self): + spoke = make_spoke() + yield_amount = spoke.apply_staking_yield(1.0) + assert yield_amount == pytest.approx(0.0) + + def test_yield_updates_total_value_usd(self): + spoke = make_spoke() + spoke.deposit("stETH", 100.0, 0.0) + value_before = spoke.total_value_usd + spoke.apply_staking_yield(1.0 / 12) # Monthly + assert spoke.total_value_usd > value_before + + +# ---------- HubRegistry.register_spoke ---------- + +class TestHubRegistryRegisterSpoke: + def test_registers_spoke_by_chain(self): + hub = HubRegistry() + spoke = make_spoke("arbitrum") + hub.register_spoke(spoke) + assert "arbitrum" in hub.spokes + + def test_registers_assets(self): + hub = HubRegistry() + spoke = make_spoke("ethereum") + hub.register_spoke(spoke) + assert "stETH" in hub.all_assets + + def test_multiple_spokes(self): + hub = HubRegistry() + hub.register_spoke(make_spoke("ethereum")) + hub.register_spoke(make_spoke("arbitrum")) + assert len(hub.spokes) == 2 + + +# ---------- HubRegistry.process_messages ---------- + +class TestHubRegistryProcessMessages: + def test_delivers_message_after_latency(self): + hub, spoke = make_hub_with_spoke() + spoke.deposit("stETH", 10.0, 0.0) + # Advance time far beyond any possible latency + delivered = hub.process_messages(1.0) + assert len(delivered) >= 1 + + def test_delivered_message_marked(self): + hub, spoke = make_hub_with_spoke() + spoke.deposit("stETH", 5.0, 0.0) + hub.process_messages(1.0) + # All pending reports should be cleared after delivery + assert len(spoke.pending_reports) == 0 + + def test_message_not_delivered_before_latency(self): + hub, spoke = make_hub_with_spoke() + spoke.deposit("stETH", 5.0, 0.0) + # Process at exactly time 0 — latency hasn't elapsed yet + # (latency is always > 0 due to base_latency + randomness) + delivered = hub.process_messages(0.0) + # No delivery expected at t=0 + assert len(delivered) == 0 + + def test_updates_global_collateral_after_delivery(self): + hub, spoke = make_hub_with_spoke() + spoke.deposit("stETH", 10.0, 0.0) + hub.process_messages(1.0) + assert hub.global_collateral.get("stETH", 0.0) == pytest.approx(10.0) + + def test_updates_total_collateral_usd(self): + hub, spoke = make_hub_with_spoke() + spoke.deposit("stETH", 5.0, 0.0) + hub.process_messages(1.0) + asset = spoke.accepted_assets[0] + expected_usd = 5.0 * asset.price * asset.weight + assert hub.total_collateral_usd == pytest.approx(expected_usd) + + +# ---------- simulate_deposit ---------- + +class TestSimulateDeposit: + def test_returns_ccip_message(self): + system = create_default_system() + msg = simulate_deposit(system, "ethereum", "stETH", 10.0, 0.0) + assert isinstance(msg, CCIPMessage) + + def test_increments_messages_sent(self): + system = create_default_system() + before = system.total_messages_sent + simulate_deposit(system, "ethereum", "stETH", 10.0, 0.0) + assert system.total_messages_sent == before + 1 + + def test_appends_to_message_log(self): + system = create_default_system() + before = len(system.message_log) + simulate_deposit(system, "arbitrum", "wstETH", 5.0, 0.0) + assert len(system.message_log) == before + 1 + + def test_spoke_balance_updated(self): + system = create_default_system() + simulate_deposit(system, "ethereum", "stETH", 50.0, 0.0) + assert system.hub.spokes["ethereum"].balances.get("stETH", 0.0) == pytest.approx(50.0) + + def test_unknown_chain_raises(self): + system = create_default_system() + with pytest.raises(ValueError, match="Unknown chain"): + simulate_deposit(system, "moon", "stETH", 1.0, 0.0) + + +# ---------- tick ---------- + +class TestTick: + def test_tick_advances_time(self): + system = create_default_system() + dt = 1.0 / 365 + tick(system, dt) + assert system.time == pytest.approx(dt) + + def test_tick_multiple_advances_time(self): + system = create_default_system() + dt = 1.0 / 365 + for _ in range(5): + tick(system, dt) + assert system.time == pytest.approx(5 * dt) + + def test_tick_with_deposits_generates_yield(self): + system = create_default_system() + simulate_deposit(system, "ethereum", "stETH", 100.0, 0.0) + # Advance past latency + tick(system, 1.0) + assert system.total_yield_generated > 0.0 + + def test_tick_returns_dict_with_expected_keys(self): + system = create_default_system() + result = tick(system, 1.0 / 365) + assert "time" in result + assert "yield_this_tick" in result + assert "messages_delivered" in result + assert "total_collateral_usd" in result + assert "per_chain" in result + + def test_tick_per_chain_has_all_chains(self): + system = create_default_system() + result = tick(system, 1.0 / 365) + for chain in ["ethereum", "arbitrum", "optimism", "base", "polygon"]: + assert chain in result["per_chain"] + + def test_tick_increases_total_messages_sent(self): + system = create_default_system() + before = system.total_messages_sent + tick(system, 1.0 / 365) + # Broadcasts state sync to all spokes each tick + assert system.total_messages_sent > before + + +# ---------- apply_price_shock ---------- + +class TestApplyPriceShock: + def test_price_shock_changes_asset_price(self): + system = create_default_system() + eth_spoke = system.hub.spokes["ethereum"] + for asset in eth_spoke.accepted_assets: + if asset.symbol == "stETH": + old_price = asset.price + break + apply_price_shock(system, "stETH", 0.5) + for asset in eth_spoke.accepted_assets: + if asset.symbol == "stETH": + assert asset.price == pytest.approx(old_price * 0.5) + break + + def test_price_shock_affects_all_chains_with_asset(self): + system = create_default_system() + apply_price_shock(system, "rETH", 0.8) + # rETH exists on ethereum and arbitrum + for chain in ["ethereum", "arbitrum"]: + spoke = system.hub.spokes[chain] + for asset in spoke.accepted_assets: + if asset.symbol == "rETH": + assert asset.price == pytest.approx(2420.0 * 0.8) + break + + def test_price_shock_recalculates_total_collateral(self): + system = create_default_system() + simulate_deposit(system, "ethereum", "stETH", 10.0, 0.0) + tick(system, 1.0) # Process messages + value_before = system.hub.total_collateral_usd + apply_price_shock(system, "stETH", 0.5) + # Value should decrease (stETH is worth less now) + # Only meaningful if there's actual stETH balance + # At minimum, recalculate_total should have run without error + # and total_collateral_usd is a valid float + assert isinstance(system.hub.total_collateral_usd, float) + + def test_price_shock_upward(self): + system = create_default_system() + eth_spoke = system.hub.spokes["ethereum"] + for asset in eth_spoke.accepted_assets: + if asset.symbol == "stETH": + old_price = asset.price + break + apply_price_shock(system, "stETH", 2.0) + for asset in eth_spoke.accepted_assets: + if asset.symbol == "stETH": + assert asset.price == pytest.approx(old_price * 2.0) + break + + +# ---------- get_crosschain_metrics ---------- + +class TestGetCrosschainMetrics: + def test_returns_all_expected_fields(self): + system = create_default_system() + m = get_crosschain_metrics(system) + assert "time" in m + assert "total_collateral_usd" in m + assert "total_messages_sent" in m + assert "total_messages_delivered" in m + assert "total_yield_generated" in m + assert "chains" in m + assert "global_collateral" in m + + def test_chains_contains_all_five(self): + system = create_default_system() + m = get_crosschain_metrics(system) + for chain in ["ethereum", "arbitrum", "optimism", "base", "polygon"]: + assert chain in m["chains"] + + def test_chain_entry_has_assets_field(self): + system = create_default_system() + m = get_crosschain_metrics(system) + for chain, data in m["chains"].items(): + assert "assets" in data + assert "total_value_usd" in data + + def test_metrics_update_after_deposit(self): + system = create_default_system() + simulate_deposit(system, "ethereum", "stETH", 50.0, 0.0) + tick(system, 1.0) # Process CCIP messages + m = get_crosschain_metrics(system) + # stETH balance should be reflected in chain assets + eth_assets = m["chains"]["ethereum"]["assets"] + assert eth_assets["stETH"]["balance"] == pytest.approx( + system.hub.spokes["ethereum"].balances.get("stETH", 0.0) + ) + + +# ---------- Full lifecycle ---------- + +class TestFullCrossChainLifecycle: + def test_deposit_on_multiple_chains_then_tick(self): + system = create_default_system() + + # Deposit on multiple chains + simulate_deposit(system, "ethereum", "stETH", 100.0, 0.0) + simulate_deposit(system, "arbitrum", "wstETH", 50.0, 0.0) + simulate_deposit(system, "base", "cbETH", 30.0, 0.0) + + assert system.total_messages_sent == 3 + + # Tick to process CCIP messages and accrue yield + result = tick(system, 1.0) + + assert system.total_messages_delivered > 0 + assert system.total_yield_generated > 0.0 + assert result["yield_this_tick"] > 0.0 + + # Metrics should be populated + m = get_crosschain_metrics(system) + assert m["total_messages_sent"] > 0 + assert m["total_yield_generated"] > 0.0 + + def test_tick_multiple_times_accumulates_yield(self): + system = create_default_system() + simulate_deposit(system, "ethereum", "stETH", 100.0, 0.0) + # Process deposit + tick(system, 1.0) + yield_after_1 = system.total_yield_generated + + # More ticks + for _ in range(10): + tick(system, 1.0 / 365) + + assert system.total_yield_generated > yield_after_1 + + def test_full_lifecycle_verify_metrics(self): + system = create_default_system() + + # Deposit on all five chains + for chain, asset in [ + ("ethereum", "stETH"), + ("arbitrum", "wstETH"), + ("optimism", "wstETH"), + ("base", "cbETH"), + ("polygon", "stMATIC"), + ]: + simulate_deposit(system, chain, asset, 100.0, 0.0) + + # Tick for one year + for _ in range(12): + tick(system, 1.0 / 12) + + m = get_crosschain_metrics(system) + assert m["time"] == pytest.approx(1.0, rel=1e-3) + assert m["total_yield_generated"] > 0.0 + assert m["total_messages_sent"] > 5 # Initial deposits + syncs + assert m["total_collateral_usd"] > 0.0 diff --git a/tests/test_risk_tranching.py b/tests/test_risk_tranching.py new file mode 100644 index 0000000..8a3e066 --- /dev/null +++ b/tests/test_risk_tranching.py @@ -0,0 +1,493 @@ +"""Tests for risk-tranched stablecoin issuance (src/primitives/risk_tranching.py).""" + +import math +import numpy as np +import pytest + +from src.primitives.risk_tranching import ( + TrancheParams, + TrancheState, + RiskTrancheSystem, + mint_capacity, + deposit_collateral, + mint_tranche, + redeem_tranche, + distribute_yield, + apply_loss, + check_liquidation, + get_tranche_metrics, +) + + +# ---------- Helpers ---------- + +def make_system(collateral: float = 0.0) -> RiskTrancheSystem: + """Return a fresh RiskTrancheSystem with optional starting collateral.""" + system = RiskTrancheSystem(params=TrancheParams()) + if collateral > 0: + deposit_collateral(system, collateral) + return system + + +def make_funded_system(collateral: float = 1_000_000.0) -> RiskTrancheSystem: + """System with collateral, all three tranches minted to a reasonable level.""" + system = make_system(collateral) + mint_tranche(system, "senior", 200_000) + mint_tranche(system, "mezzanine", 100_000) + mint_tranche(system, "junior", 50_000) + return system + + +# ---------- TrancheParams ---------- + +class TestTrancheParams: + def test_defaults(self): + p = TrancheParams() + assert p.senior_collateral_ratio == 1.5 + assert p.mezzanine_collateral_ratio == 1.2 + assert p.senior_yield_target == 0.03 + assert p.mezzanine_yield_target == 0.08 + assert p.max_senior_fraction == 0.50 + assert p.max_mezzanine_fraction == 0.30 + assert p.senior_liquidation_ratio == 1.2 + assert p.mezzanine_liquidation_ratio == 1.05 + assert p.rebalance_threshold == 0.05 + + def test_custom_params(self): + p = TrancheParams(senior_collateral_ratio=2.0, senior_yield_target=0.05) + assert p.senior_collateral_ratio == 2.0 + assert p.senior_yield_target == 0.05 + + +# ---------- TrancheState ---------- + +class TestTrancheState: + def test_collateral_ratio_zero_supply_is_inf(self): + t = TrancheState(name="test", supply=0.0, collateral_backing=100.0) + assert t.collateral_ratio == float("inf") + + def test_collateral_ratio_normal(self): + t = TrancheState(name="test", supply=1000.0, collateral_backing=1500.0) + assert t.collateral_ratio == pytest.approx(1.5) + + def test_collateral_ratio_undercollateralized(self): + t = TrancheState(name="test", supply=1000.0, collateral_backing=800.0) + assert t.collateral_ratio == pytest.approx(0.8) + + def test_is_healthy_above_one(self): + t = TrancheState(name="test", supply=100.0, collateral_backing=150.0) + assert t.is_healthy is True + + def test_is_healthy_below_one(self): + t = TrancheState(name="test", supply=100.0, collateral_backing=90.0) + assert t.is_healthy is False + + def test_is_healthy_zero_supply(self): + # CR is inf when supply is 0, inf > 1 → healthy + t = TrancheState(name="test", supply=0.0, collateral_backing=0.0) + assert t.is_healthy is True + + +# ---------- RiskTrancheSystem creation ---------- + +class TestRiskTrancheSystemCreation: + def test_default_names(self): + system = RiskTrancheSystem(params=TrancheParams()) + assert system.senior.name == "myUSD-S" + assert system.mezzanine.name == "myUSD-M" + assert system.junior.name == "$MYCO" + + def test_initial_zero_state(self): + system = RiskTrancheSystem(params=TrancheParams()) + assert system.total_collateral == 0.0 + assert system.total_supply == 0.0 + assert system.system_collateral_ratio == float("inf") + + def test_total_supply_aggregates_tranches(self): + system = RiskTrancheSystem(params=TrancheParams()) + system.senior.supply = 100.0 + system.mezzanine.supply = 50.0 + system.junior.supply = 25.0 + assert system.total_supply == pytest.approx(175.0) + + def test_system_collateral_ratio(self): + system = RiskTrancheSystem(params=TrancheParams()) + system.total_collateral = 300.0 + system.senior.supply = 100.0 + system.mezzanine.supply = 50.0 + assert system.system_collateral_ratio == pytest.approx(300.0 / 150.0) + + +# ---------- deposit_collateral ---------- + +class TestDepositCollateral: + def test_deposit_increases_total(self): + system = make_system() + deposit_collateral(system, 500.0) + assert system.total_collateral == pytest.approx(500.0) + + def test_multiple_deposits_accumulate(self): + system = make_system() + deposit_collateral(system, 300.0) + deposit_collateral(system, 200.0) + assert system.total_collateral == pytest.approx(500.0) + + def test_deposit_into_existing_system(self): + system = make_funded_system(1_000_000.0) + before = system.total_collateral + deposit_collateral(system, 100_000.0) + assert system.total_collateral == pytest.approx(before + 100_000.0) + + def test_returns_system(self): + system = make_system() + result = deposit_collateral(system, 100.0) + assert result is system + + +# ---------- mint_capacity ---------- + +class TestMintCapacity: + def test_no_collateral_returns_zeros(self): + system = make_system(0.0) + caps = mint_capacity(system) + assert caps["senior"] == pytest.approx(0.0) + assert caps["mezzanine"] == pytest.approx(0.0) + assert caps["junior"] >= 0.0 + + def test_senior_capacity_within_fraction_limit(self): + system = make_system(1_000_000.0) + caps = mint_capacity(system) + # Max senior = (1e6 * 0.50) / 1.5 = 333_333.33 + expected_senior = (1_000_000.0 * 0.50) / 1.5 + assert caps["senior"] == pytest.approx(expected_senior) + + def test_mezzanine_capacity_within_fraction_limit(self): + system = make_system(1_000_000.0) + caps = mint_capacity(system) + # Max mez = (1e6 * 0.30) / 1.2 = 250_000 + expected_mez = (1_000_000.0 * 0.30) / 1.2 + assert caps["mezzanine"] == pytest.approx(expected_mez) + + def test_capacity_decreases_after_minting(self): + system = make_system(1_000_000.0) + caps_before = mint_capacity(system) + mint_tranche(system, "senior", 100_000.0) + caps_after = mint_capacity(system) + assert caps_after["senior"] < caps_before["senior"] + + def test_all_keys_present(self): + system = make_system(500_000.0) + caps = mint_capacity(system) + assert "senior" in caps + assert "mezzanine" in caps + assert "junior" in caps + + +# ---------- mint_tranche ---------- + +class TestMintTranche: + def test_mint_senior_returns_correct_amount(self): + system = make_system(1_000_000.0) + system, minted = mint_tranche(system, "senior", 100_000.0) + assert minted == pytest.approx(100_000.0) + assert system.senior.supply == pytest.approx(100_000.0) + + def test_mint_mezzanine(self): + system = make_system(1_000_000.0) + system, minted = mint_tranche(system, "mezzanine", 50_000.0) + assert minted == pytest.approx(50_000.0) + assert system.mezzanine.supply == pytest.approx(50_000.0) + + def test_mint_junior(self): + system = make_system(1_000_000.0) + system, minted = mint_tranche(system, "junior", 30_000.0) + assert minted == pytest.approx(30_000.0) + assert system.junior.supply == pytest.approx(30_000.0) + + def test_mint_respects_capacity_cap(self): + system = make_system(100.0) + # Request far more than capacity + system, minted = mint_tranche(system, "senior", 1_000_000.0) + caps = mint_capacity(make_system(100.0)) + assert minted <= caps["senior"] + 1e-9 + + def test_mint_when_no_collateral_returns_zero(self): + system = make_system(0.0) + system, minted = mint_tranche(system, "senior", 1_000.0) + assert minted == pytest.approx(0.0) + assert system.senior.supply == pytest.approx(0.0) + + def test_mint_collateral_backing_correct_senior(self): + system = make_system(1_000_000.0) + system, minted = mint_tranche(system, "senior", 100_000.0) + expected_backing = 100_000.0 * system.params.senior_collateral_ratio + assert system.senior.collateral_backing == pytest.approx(expected_backing) + + def test_mint_collateral_backing_correct_mezzanine(self): + system = make_system(1_000_000.0) + system, minted = mint_tranche(system, "mezzanine", 50_000.0) + expected_backing = 50_000.0 * system.params.mezzanine_collateral_ratio + assert system.mezzanine.collateral_backing == pytest.approx(expected_backing) + + def test_mint_junior_one_to_one(self): + system = make_system(1_000_000.0) + system, minted = mint_tranche(system, "junior", 10_000.0) + assert system.junior.collateral_backing == pytest.approx(minted) + + +# ---------- redeem_tranche ---------- + +class TestRedeemTranche: + def test_redeem_returns_proportional_collateral(self): + system = make_system(1_000_000.0) + mint_tranche(system, "senior", 100_000.0) + backing_before = system.senior.collateral_backing + # Redeem half + system, collateral = redeem_tranche(system, "senior", 50_000.0) + assert collateral == pytest.approx(backing_before / 2) + + def test_redeem_reduces_supply(self): + system = make_system(1_000_000.0) + mint_tranche(system, "senior", 100_000.0) + system, _ = redeem_tranche(system, "senior", 40_000.0) + assert system.senior.supply == pytest.approx(60_000.0) + + def test_redeem_reduces_total_collateral(self): + system = make_system(1_000_000.0) + mint_tranche(system, "senior", 100_000.0) + total_before = system.total_collateral + system, collateral = redeem_tranche(system, "senior", 50_000.0) + assert system.total_collateral == pytest.approx(total_before - collateral) + + def test_redeem_capped_at_supply(self): + system = make_system(1_000_000.0) + mint_tranche(system, "mezzanine", 50_000.0) + # Try to redeem more than supply + system, collateral = redeem_tranche(system, "mezzanine", 999_999.0) + assert system.mezzanine.supply == pytest.approx(0.0) + + def test_redeem_zero_supply_returns_nothing(self): + system = make_system(1_000_000.0) + system, collateral = redeem_tranche(system, "junior", 100.0) + assert collateral == pytest.approx(0.0) + + +# ---------- distribute_yield ---------- + +class TestDistributeYield: + def test_senior_gets_target_first(self): + system = make_funded_system(1_000_000.0) + senior_supply = system.senior.supply + dt = 1.0 # 1 year + # Small yield that only covers senior target + senior_target = senior_supply * system.params.senior_yield_target * dt + yield_amount = senior_target * 0.5 # Only half of what senior wants + + senior_before = system.senior.accrued_yield + mez_before = system.mezzanine.accrued_yield + distribute_yield(system, yield_amount, dt) + + assert system.senior.accrued_yield > senior_before + # Mezzanine should not receive anything when yield is insufficient for senior + assert system.mezzanine.accrued_yield == pytest.approx(mez_before) + + def test_mezzanine_gets_yield_after_senior(self): + system = make_funded_system(1_000_000.0) + dt = 1.0 + senior_target = system.senior.supply * system.params.senior_yield_target * dt + mez_target = system.mezzanine.supply * system.params.mezzanine_yield_target * dt + # Yield that covers senior and some for mezzanine + yield_amount = senior_target + mez_target * 0.5 + + distribute_yield(system, yield_amount, dt) + assert system.mezzanine.accrued_yield > 0.0 + + def test_junior_gets_residual(self): + system = make_funded_system(1_000_000.0) + dt = 1.0 + senior_target = system.senior.supply * system.params.senior_yield_target * dt + mez_target = system.mezzanine.supply * system.params.mezzanine_yield_target * dt + # Large yield that satisfies senior + mez, leaves some for junior + extra = 50_000.0 + yield_amount = senior_target + mez_target + extra + + distribute_yield(system, yield_amount, dt) + assert system.junior.accrued_yield == pytest.approx(extra) + + def test_cumulative_yield_updates(self): + system = make_funded_system(1_000_000.0) + distribute_yield(system, 10_000.0, 1.0 / 365) + total_distributed = ( + system.senior.cumulative_yield + + system.mezzanine.cumulative_yield + + system.junior.cumulative_yield + ) + assert total_distributed == pytest.approx(10_000.0) + + def test_yield_updates_total_staking_yield(self): + system = make_funded_system(1_000_000.0) + distribute_yield(system, 5_000.0, 1.0 / 365) + assert system.total_staking_yield == pytest.approx(5_000.0) + + def test_yield_increases_collateral_backing(self): + system = make_funded_system(1_000_000.0) + senior_backing_before = system.senior.collateral_backing + dt = 1.0 + yield_amount = system.senior.supply * system.params.senior_yield_target * dt + distribute_yield(system, yield_amount, dt) + assert system.senior.collateral_backing > senior_backing_before + + +# ---------- apply_loss ---------- + +class TestApplyLoss: + def test_junior_absorbs_first(self): + system = make_funded_system(1_000_000.0) + junior_backing = system.junior.collateral_backing + mez_backing_before = system.mezzanine.collateral_backing + # Small loss that junior can fully absorb + small_loss = junior_backing * 0.5 + apply_loss(system, small_loss) + assert system.junior.collateral_backing == pytest.approx(junior_backing - small_loss) + assert system.mezzanine.collateral_backing == pytest.approx(mez_backing_before) + + def test_mezzanine_absorbs_after_junior_exhausted(self): + system = make_funded_system(1_000_000.0) + junior_backing = system.junior.collateral_backing + mez_backing_before = system.mezzanine.collateral_backing + # Loss larger than junior can handle + loss = junior_backing + 10_000.0 + apply_loss(system, loss) + assert system.junior.collateral_backing == pytest.approx(0.0) + assert system.mezzanine.collateral_backing < mez_backing_before + + def test_senior_absorbs_last(self): + system = make_funded_system(1_000_000.0) + junior_backing = system.junior.collateral_backing + mez_backing = system.mezzanine.collateral_backing + senior_backing_before = system.senior.collateral_backing + # Wipe out junior + mez, then hit senior + massive_loss = junior_backing + mez_backing + 5_000.0 + apply_loss(system, massive_loss) + assert system.junior.collateral_backing == pytest.approx(0.0) + assert system.mezzanine.collateral_backing == pytest.approx(0.0) + assert system.senior.collateral_backing < senior_backing_before + + def test_total_collateral_decreases(self): + system = make_funded_system(1_000_000.0) + total_before = system.total_collateral + apply_loss(system, 50_000.0) + assert system.total_collateral == pytest.approx(total_before - 50_000.0) + + def test_cumulative_losses_tracked(self): + system = make_funded_system(1_000_000.0) + apply_loss(system, system.junior.collateral_backing) + assert system.junior.cumulative_losses > 0.0 + + def test_loss_exceeding_all_collateral(self): + system = make_funded_system(1_000_000.0) + total_backing = ( + system.junior.collateral_backing + + system.mezzanine.collateral_backing + + system.senior.collateral_backing + ) + # Loss beyond all collateral + apply_loss(system, total_backing * 2) + # All three should be at or near zero + assert system.junior.collateral_backing <= 0.0 + assert system.mezzanine.collateral_backing <= 0.0 + + +# ---------- check_liquidation ---------- + +class TestCheckLiquidation: + def test_no_liquidation_when_healthy(self): + system = make_funded_system(1_000_000.0) + result = check_liquidation(system) + assert result["senior"] is False + assert result["mezzanine"] is False + assert result["junior"] is False + + def test_senior_liquidation_triggered(self): + system = make_funded_system(1_000_000.0) + # Force senior CR below liquidation threshold (1.2) + system.senior.collateral_backing = system.senior.supply * 1.1 # 110% < 120% + result = check_liquidation(system) + assert result["senior"] is True + + def test_mezzanine_liquidation_triggered(self): + system = make_funded_system(1_000_000.0) + # Force mez CR below 1.05 + system.mezzanine.collateral_backing = system.mezzanine.supply * 1.0 # 100% < 105% + result = check_liquidation(system) + assert result["mezzanine"] is True + + def test_no_liquidation_when_zero_supply(self): + # With zero supply, nothing to liquidate + system = make_system(1_000_000.0) + result = check_liquidation(system) + assert result["senior"] is False + assert result["mezzanine"] is False + assert result["junior"] is False + + def test_all_keys_present(self): + system = make_system() + result = check_liquidation(system) + assert "senior" in result + assert "mezzanine" in result + assert "junior" in result + + +# ---------- get_tranche_metrics ---------- + +class TestGetTrancheMetrics: + def test_metrics_structure(self): + system = make_funded_system(1_000_000.0) + m = get_tranche_metrics(system) + assert "total_collateral" in m + assert "system_cr" in m + assert "total_supply" in m + assert "senior" in m + assert "mezzanine" in m + assert "junior" in m + + def test_metrics_values_consistent(self): + system = make_funded_system(1_000_000.0) + m = get_tranche_metrics(system) + assert m["senior"]["supply"] == system.senior.supply + assert m["mezzanine"]["supply"] == system.mezzanine.supply + assert m["junior"]["supply"] == system.junior.supply + + +# ---------- Full lifecycle ---------- + +class TestFullLifecycle: + def test_deposit_mint_yield_loss_verify_crs(self): + system = make_system(2_000_000.0) + + # Mint all three tranches + system, s_minted = mint_tranche(system, "senior", 300_000.0) + system, m_minted = mint_tranche(system, "mezzanine", 150_000.0) + system, j_minted = mint_tranche(system, "junior", 100_000.0) + + assert s_minted > 0 + assert m_minted > 0 + assert j_minted > 0 + + # Distribute yield + distribute_yield(system, 50_000.0, 1.0 / 12) # Monthly yield + + # Tranches should have gained collateral backing + assert system.senior.collateral_backing > s_minted * system.params.senior_collateral_ratio + + # Apply a moderate loss + junior_backing_before = system.junior.collateral_backing + apply_loss(system, junior_backing_before * 0.3) + + # Junior should have absorbed the loss + assert system.junior.collateral_backing < junior_backing_before + # Senior should be untouched + assert system.senior.cumulative_losses == pytest.approx(0.0) + + # Collateral ratios: senior should be healthy + assert system.senior.collateral_ratio > system.params.senior_liquidation_ratio