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

188 lines
6.3 KiB
Python

"""Cross-chain collateral visualization 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("Cross-Chain Collateral Map")
st.caption("Visualize collateral distribution across chains and staking assets.")
# Configuration
col1, col2 = st.columns(2)
with col1:
n_days = st.slider("Simulation days", 30, 365, 90, key="cc_days")
with col2:
eth_vol = st.select_slider(
"ETH Volatility",
options=[0.2, 0.4, 0.6, 0.8, 1.0],
value=0.6,
key="cc_vol",
)
if st.button("Run Cross-Chain Simulation", key="cc_run"):
with st.spinner("Simulating cross-chain system..."):
from src.crosschain.hub_spoke import (
create_default_system, simulate_deposit, tick,
apply_price_shock, get_crosschain_metrics,
)
system = create_default_system()
# Bootstrap with deposits
deposits = {
"ethereum": [("stETH", 100), ("rETH", 50), ("cbETH", 30)],
"arbitrum": [("wstETH", 80), ("rETH", 40)],
"optimism": [("wstETH", 60), ("sfrxETH", 40)],
"base": [("cbETH", 40), ("USDC", 100_000)],
"polygon": [("stMATIC", 200_000), ("USDC", 50_000)],
}
for chain, assets in deposits.items():
for symbol, qty in assets:
simulate_deposit(system, chain, symbol, qty, 0.0)
system.hub.process_messages(0.0)
# Simulate daily
dt = 1 / 365
history = []
for day in range(n_days):
# Random deposits
if np.random.random() < 0.3:
chains = list(system.hub.spokes.keys())
chain = np.random.choice(chains)
spoke = system.hub.spokes[chain]
if spoke.accepted_assets:
asset = np.random.choice(spoke.accepted_assets)
qty = np.random.lognormal(2, 1) / asset.price
simulate_deposit(system, chain, asset.symbol, qty, system.time)
# Price movements
eth_return = np.random.normal(0, eth_vol * np.sqrt(dt))
for chain, spoke in system.hub.spokes.items():
for asset in spoke.accepted_assets:
if asset.symbol != "USDC":
asset.price *= (1 + eth_return + np.random.normal(0, 0.01))
spoke._recalculate_value()
system.hub._recalculate_total()
metrics = tick(system, dt)
metrics["day"] = day
history.append(metrics)
# Visualization
_plot_chain_distribution(history, system)
_plot_collateral_timeline(history)
_plot_asset_breakdown(system)
def _plot_chain_distribution(history: list, system):
"""Stacked area chart of collateral per chain over time."""
st.subheader("Collateral by Chain")
fig = go.Figure()
chains = list(system.hub.spokes.keys())
colors = {
"ethereum": "#627EEA",
"arbitrum": "#28A0F0",
"optimism": "#FF0420",
"base": "#0052FF",
"polygon": "#8247E5",
}
days = [h["day"] for h in history]
for chain in chains:
values = [h["per_chain"].get(chain, 0) for h in history]
fig.add_trace(go.Scatter(
x=days, y=values,
name=chain.capitalize(),
stackgroup="one",
fillcolor=colors.get(chain, "#888888"),
line=dict(width=0.5, color=colors.get(chain, "#888888")),
))
fig.update_layout(
title="Cross-Chain Collateral Distribution",
xaxis_title="Day",
yaxis_title="USD Value",
template="plotly_dark",
height=500,
)
st.plotly_chart(fig, use_container_width=True)
def _plot_collateral_timeline(history: list):
"""Total collateral and yield over time."""
st.subheader("Total Collateral & Yield")
fig = make_subplots(specs=[[{"secondary_y": True}]])
days = [h["day"] for h in history]
total_col = [h["total_collateral_usd"] for h in history]
cum_yield = [h["yield_this_tick"] for h in history]
cum_yield_sum = np.cumsum(cum_yield)
fig.add_trace(
go.Scatter(x=days, y=total_col, name="Total Collateral", line=dict(color="#2dd4bf", width=2)),
secondary_y=False,
)
fig.add_trace(
go.Scatter(x=days, y=cum_yield_sum, name="Cumulative Yield", line=dict(color="#fbbf24", width=2)),
secondary_y=True,
)
fig.update_layout(
title="System Collateral & Staking Yield",
template="plotly_dark",
height=400,
)
fig.update_yaxes(title_text="Collateral (USD)", secondary_y=False)
fig.update_yaxes(title_text="Cumulative Yield (USD)", secondary_y=True)
st.plotly_chart(fig, use_container_width=True)
def _plot_asset_breakdown(system):
"""Treemap of current asset breakdown."""
st.subheader("Current Asset Breakdown")
labels, parents, values, colors = [], [], [], []
chain_colors = {
"ethereum": "#627EEA",
"arbitrum": "#28A0F0",
"optimism": "#FF0420",
"base": "#0052FF",
"polygon": "#8247E5",
}
for chain, spoke in system.hub.spokes.items():
labels.append(chain.capitalize())
parents.append("")
values.append(spoke.total_value_usd)
colors.append(chain_colors.get(chain, "#888"))
for asset in spoke.accepted_assets:
qty = spoke.balances.get(asset.symbol, 0)
val = qty * asset.price
if val > 0:
labels.append(f"{asset.symbol}")
parents.append(chain.capitalize())
values.append(val)
colors.append(chain_colors.get(chain, "#888"))
fig = go.Figure(go.Treemap(
labels=labels,
parents=parents,
values=values,
marker_colors=colors,
textinfo="label+value+percent root",
texttemplate="%{label}<br>$%{value:,.0f}<br>%{percentRoot:.1%}",
))
fig.update_layout(
title="Collateral Treemap",
template="plotly_dark",
height=500,
)
st.plotly_chart(fig, use_container_width=True)