myco-bonding-curve/dashboard/tabs/cadcad_sim.py

172 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""cadCAD Monte Carlo simulation dashboard tab."""
import streamlit as st
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
def render():
st.header("cadCAD Monte Carlo Simulation")
st.caption("Full system simulation with stochastic processes, parameter sweeps, and stress testing.")
scenario = st.selectbox("Scenario", [
"Normal Growth",
"ETH Crash Stress Test",
"Parameter Sweep",
])
col1, col2, col3 = st.columns(3)
with col1:
timesteps = st.slider("Days", 30, 730, 180, key="cd_days")
with col2:
runs = st.slider("Monte Carlo Runs", 1, 10, 3, key="cd_runs")
with col3:
if scenario == "ETH Crash Stress Test":
crash_mag = st.slider("Crash Magnitude %", 20, 80, 50, 5, key="cd_crash")
elif scenario == "Parameter Sweep":
st.info("Sweeps: volatility × growth × redemption")
if st.button("Run cadCAD Simulation", key="cd_run"):
with st.spinner(f"Running {scenario} ({runs} runs, {timesteps} days)..."):
try:
from src.cadcad.config import (
scenario_normal_growth,
scenario_stress_test,
scenario_parameter_sweep,
)
if scenario == "Normal Growth":
df = scenario_normal_growth(timesteps=timesteps, runs=runs)
elif scenario == "ETH Crash Stress Test":
df = scenario_stress_test(timesteps=timesteps, runs=runs)
else:
df = scenario_parameter_sweep(timesteps=timesteps, runs=1)
st.session_state["cadcad_results"] = df
st.success(f"Simulation complete: {len(df)} data points across {runs} run(s)")
except Exception as e:
st.error(f"Simulation error: {e}")
st.info("Make sure cadCAD is installed: `pip install cadCAD`")
return
# Display results
if "cadcad_results" in st.session_state:
df = st.session_state["cadcad_results"]
_plot_overview(df)
_plot_tranches(df)
_plot_crosschain(df)
def _plot_overview(df):
"""System overview: supply, collateral, price, CR."""
st.subheader("System Overview")
fig = make_subplots(
rows=2, cols=2,
subplot_titles=("Total Supply", "Total Collateral", "MYCO Price", "System CR"),
)
runs = df["run"].unique()
colors = ["#2dd4bf", "#f59e0b", "#ef4444", "#a78bfa", "#60a5fa"]
for i, run in enumerate(runs):
rd = df[df["run"] == run]
color = colors[i % len(colors)]
opacity = 0.8 if len(runs) == 1 else 0.4
fig.add_trace(go.Scatter(
x=rd["timestep"], y=rd["total_supply"],
name=f"Run {run}" if i == 0 else None, showlegend=(i == 0),
line=dict(color=color, width=1), opacity=opacity,
), row=1, col=1)
fig.add_trace(go.Scatter(
x=rd["timestep"], y=rd["total_collateral_usd"],
showlegend=False, line=dict(color=color, width=1), opacity=opacity,
), row=1, col=2)
fig.add_trace(go.Scatter(
x=rd["timestep"], y=rd["myco_price"],
showlegend=False, line=dict(color=color, width=1), opacity=opacity,
), row=2, col=1)
fig.add_trace(go.Scatter(
x=rd["timestep"], y=rd["system_cr"].clip(upper=5),
showlegend=False, line=dict(color=color, width=1), opacity=opacity,
), row=2, col=2)
fig.update_layout(template="plotly_dark", height=600, showlegend=True)
st.plotly_chart(fig, use_container_width=True)
def _plot_tranches(df):
"""Tranche metrics across runs."""
st.subheader("Tranche Dynamics")
fig = make_subplots(
rows=1, cols=3,
subplot_titles=("Senior CR", "Mezzanine CR", "Junior CR"),
)
runs = df["run"].unique()
for i, run in enumerate(runs):
rd = df[df["run"] == run]
opacity = 0.6
for j, (col, color) in enumerate([
("senior_cr", "#10B981"),
("mezzanine_cr", "#F59E0B"),
("junior_cr", "#EF4444"),
]):
fig.add_trace(go.Scatter(
x=rd["timestep"], y=rd[col].clip(upper=5),
showlegend=False, line=dict(color=color, width=1), opacity=opacity,
), row=1, col=j + 1)
# Add threshold lines
fig.add_hline(y=1.5, line_dash="dash", line_color="#10B981", row=1, col=1)
fig.add_hline(y=1.2, line_dash="dash", line_color="#F59E0B", row=1, col=2)
fig.add_hline(y=1.0, line_dash="dash", line_color="#EF4444", row=1, col=3)
fig.update_layout(template="plotly_dark", height=350)
st.plotly_chart(fig, use_container_width=True)
def _plot_crosschain(df):
"""Cross-chain metrics."""
st.subheader("Cross-Chain Activity")
# Check for per-chain columns
chain_cols = [c for c in df.columns if c.startswith("collateral_")]
if not chain_cols:
st.info("No per-chain data in this simulation.")
return
fig = go.Figure()
rd = df[df["run"] == df["run"].iloc[0]] # First run
for col in chain_cols:
chain_name = col.replace("collateral_", "").capitalize()
fig.add_trace(go.Scatter(
x=rd["timestep"], y=rd[col],
name=chain_name, stackgroup="one",
))
fig.update_layout(
title="Collateral by Chain (Run 1)",
xaxis_title="Timestep", yaxis_title="USD Value",
template="plotly_dark", height=400,
)
st.plotly_chart(fig, use_container_width=True)
# Summary stats
st.subheader("Summary Statistics")
last_step = df[df["timestep"] == df["timestep"].max()]
cols = st.columns(4)
cols[0].metric("Avg Final Supply", f"{last_step['total_supply'].mean():,.0f}")
cols[1].metric("Avg Final Collateral", f"${last_step['total_collateral_usd'].mean():,.0f}")
cols[2].metric("Avg System CR", f"{last_step['system_cr'].mean():.2f}")
cols[3].metric("Total CCIP Messages", f"{last_step['ccip_messages'].mean():,.0f}")