"""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