myco-bonding-curve/dashboard/charts.py

204 lines
6.4 KiB
Python

"""Plotly figure factories for the MYCO dashboard.
All functions return go.Figure instances — no Streamlit dependency.
"""
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def fig_simulation_overview(result) -> go.Figure:
"""2x2 subplot: supply, reserve, backing ratio, minting breakdown."""
fig = make_subplots(
rows=2, cols=2,
subplot_titles=("Supply", "Reserve Value", "Backing Ratio", "Minting"),
)
t = result.times.tolist()
fig.add_trace(
go.Scatter(x=t, y=result.supply.tolist(), name="Supply", line=dict(color="#2196F3")),
row=1, col=1,
)
fig.add_trace(
go.Scatter(x=t, y=result.reserve_value.tolist(), name="Reserve", line=dict(color="#4CAF50")),
row=1, col=2,
)
fig.add_trace(
go.Scatter(x=t, y=result.backing_ratio.tolist(), name="Backing Ratio", line=dict(color="#FF9800")),
row=2, col=1,
)
fig.add_trace(
go.Scatter(x=t, y=result.financial_minted.tolist(), name="Financial", line=dict(color="#9C27B0")),
row=2, col=2,
)
fig.add_trace(
go.Scatter(x=t, y=result.commitment_minted.tolist(), name="Commitment", line=dict(color="#E91E63")),
row=2, col=2,
)
fig.update_layout(height=600, showlegend=True, template="plotly_white")
return fig
def fig_dca_comparison(results: dict) -> go.Figure:
"""Chunk prices + cumulative tokens for DCA strategies."""
fig = make_subplots(
rows=1, cols=2,
subplot_titles=("Price per Chunk", "Cumulative Tokens"),
)
colors = {"fixed": "#2196F3", "twap_aware": "#FF9800"}
for name, dca_result in results.items():
history = dca_result.order.history
chunks_x = list(range(len(history)))
prices = [h["price"] for h in history]
tokens = [h["tokens"] for h in history]
import numpy as np
cumulative = np.cumsum(tokens).tolist()
fig.add_trace(
go.Scatter(x=chunks_x, y=prices, name=f"{name} price",
mode="lines+markers", line=dict(color=colors[name])),
row=1, col=1,
)
fig.add_trace(
go.Scatter(x=chunks_x, y=cumulative, name=f"{name} cumulative",
mode="lines+markers", line=dict(color=colors[name])),
row=1, col=2,
)
# Lump sum reference
for name, dca_result in results.items():
fig.add_hline(
y=dca_result.lump_sum_tokens, row=1, col=2,
line_dash="dash", line_color="gray",
annotation_text="lump sum" if name == "fixed" else None,
)
break
fig.update_layout(height=400, template="plotly_white")
return fig
def fig_signal_routing(routing: dict) -> go.Figure:
"""3-panel: price+TWAP, deviation+volatility, adaptive params."""
fig = make_subplots(
rows=3, cols=1, shared_xaxes=True,
subplot_titles=("Price Trajectory", "Signals", "Adaptive Parameters"),
)
t = routing["times"]
fig.add_trace(
go.Scatter(x=t, y=routing.get("prices", t), name="Spot Price",
line=dict(color="#2196F3")),
row=1, col=1,
)
fig.add_trace(
go.Scatter(x=t, y=routing["twap_deviation"], name="TWAP Deviation",
line=dict(color="#FF5722")),
row=2, col=1,
)
fig.add_trace(
go.Scatter(x=t, y=routing["volatility"], name="Volatility",
line=dict(color="#9C27B0")),
row=2, col=1,
)
fig.add_trace(
go.Scatter(x=t, y=routing["flow_threshold"], name="Flow Threshold",
line=dict(color="#4CAF50")),
row=3, col=1,
)
fig.add_trace(
go.Scatter(x=t, y=routing["surge_fee_rate"], name="Surge Fee",
line=dict(color="#FF9800")),
row=3, col=1,
)
fig.update_layout(height=700, template="plotly_white")
return fig
def fig_bank_run_sweep(results: dict) -> go.Figure:
"""Overlaid reserve curves for different redemption fractions."""
fig = go.Figure()
colors = ["#2196F3", "#FF9800", "#E91E63", "#4CAF50", "#9C27B0"]
for i, (frac, result) in enumerate(results.items()):
fig.add_trace(go.Scatter(
x=result.times.tolist(),
y=result.reserve_value.tolist(),
name=f"{frac:.0%} redemption",
line=dict(color=colors[i % len(colors)]),
))
fig.update_layout(
title="Bank Run Stress Test — Reserve Curves",
xaxis_title="Time",
yaxis_title="Reserve Value",
height=500,
template="plotly_white",
)
return fig
def fig_crdt_divergence(sim_result: dict) -> go.Figure:
"""Peer divergence timeline + merge event scatter."""
fig = make_subplots(
rows=2, cols=1, shared_xaxes=True,
subplot_titles=("Peer Divergence", "Network Events"),
)
t = sim_result["times"]
# Divergence line
fig.add_trace(
go.Scatter(x=t, y=sim_result["divergence"], name="Divergence",
line=dict(color="#2196F3", width=2)),
row=1, col=1,
)
fig.add_hline(y=1, line_dash="dash", line_color="green", row=1, col=1)
# Shade partitions
for i, active in enumerate(sim_result["partition_active"]):
if active:
fig.add_vrect(
x0=t[i] - 0.5, x1=t[i] + 0.5,
fillcolor="red", opacity=0.1, line_width=0,
row=1, col=1,
)
# Event scatter
from src.crdt.bridge.events import EventType
events = sim_result.get("events", [])
peer_ids = list(sim_result.get("peer_signatures", {}).keys())
peer_y = {pid: i for i, pid in enumerate(peer_ids)}
for etype, color, marker, label in [
(EventType.MERGE, "blue", "circle", "Merge"),
(EventType.PARTITION, "red", "x", "Partition"),
(EventType.RECONNECT, "green", "triangle-up", "Reconnect"),
]:
filtered = [e for e in events if e.event_type == etype]
if filtered:
fig.add_trace(go.Scatter(
x=[e.time for e in filtered],
y=[peer_y.get(e.source_peer, 0) for e in filtered],
mode="markers",
marker=dict(color=color, symbol=marker, size=8),
name=label,
), row=2, col=1)
fig.update_yaxes(
tickvals=list(range(len(peer_ids))),
ticktext=peer_ids,
row=2, col=1,
)
fig.update_layout(height=600, template="plotly_white")
return fig