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

191 lines
7.0 KiB
Python
Raw Permalink 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.

"""Conviction voting governance 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("Conviction Voting Governance")
st.caption("Explore conviction voting dynamics for parameter governance.")
# Parameters
col1, col2, col3, col4 = st.columns(4)
with col1:
half_life = st.slider("Half-life (epochs)", 2, 30, 7, key="cv_hl")
with col2:
beta = st.slider("Max fund share (β)", 0.05, 0.5, 0.2, 0.05, key="cv_beta")
with col3:
rho = st.slider("Scale factor (ρ) ×1000", 0.5, 10.0, 2.5, 0.5, key="cv_rho")
with col4:
n_voters = st.slider("Voters", 5, 50, 20, key="cv_voters")
rho_actual = rho / 1000
tab_curves, tab_sim, tab_trigger = st.tabs(["Conviction Curves", "Governance Sim", "Trigger Map"])
with tab_curves:
_render_conviction_curves(half_life)
with tab_sim:
_render_governance_sim(half_life, beta, rho_actual, n_voters)
with tab_trigger:
_render_trigger_map(beta, rho_actual)
def _render_conviction_curves(half_life: float):
"""Visualize conviction charging and discharging."""
from src.primitives.conviction import ConvictionParams, generate_conviction_curves
params = ConvictionParams.from_half_life(half_life)
st.markdown(f"**α = {params.alpha:.4f}** (half-life = {half_life} epochs)")
curves = generate_conviction_curves(1000, params.alpha, epochs=100)
fig = make_subplots(rows=1, cols=2, subplot_titles=("Charging", "Discharging"))
fig.add_trace(go.Scatter(
x=curves["time"], y=curves["charge"],
name="Conviction", line=dict(color="#2dd4bf", width=2),
), row=1, col=1)
fig.add_trace(go.Scatter(
x=curves["time"], y=curves["max"],
name="Max", line=dict(color="#fbbf24", dash="dash"),
), row=1, col=1)
fig.add_trace(go.Scatter(
x=curves["time"], y=curves["discharge"],
name="Decay", line=dict(color="#ef4444", width=2),
), row=1, col=2)
fig.update_layout(template="plotly_dark", height=400, showlegend=True)
fig.update_xaxes(title_text="Epochs")
fig.update_yaxes(title_text="Conviction")
st.plotly_chart(fig, use_container_width=True)
# Key metrics
from src.primitives.conviction import epochs_to_fraction, max_conviction
col1, col2, col3 = st.columns(3)
col1.metric("50% of max", f"{epochs_to_fraction(0.5, params.alpha):.1f} epochs")
col2.metric("90% of max", f"{epochs_to_fraction(0.9, params.alpha):.1f} epochs")
col3.metric("Max conviction (1000 tokens)", f"{max_conviction(1000, params.alpha):,.0f}")
def _render_governance_sim(half_life: float, beta: float, rho: float, n_voters: int):
"""Run a governance simulation."""
if st.button("Run Governance Simulation", key="cv_run"):
with st.spinner("Simulating conviction voting..."):
from src.primitives.conviction import (
ConvictionParams, ConvictionSystem, Proposal, Voter,
stake, tick, get_governance_metrics,
)
params = ConvictionParams.from_half_life(half_life, beta=beta, rho=rho)
system = ConvictionSystem(params=params)
# Create voters
for i in range(n_voters):
holdings = np.random.lognormal(mean=np.log(5000), sigma=1.0)
system.voters[f"v{i}"] = Voter(id=f"v{i}", holdings=holdings)
system.total_supply = sum(v.holdings for v in system.voters.values())
# Create proposals
proposals = [
("Lower senior CR to 1.3", 0.05),
("Add Scroll chain", 0.08),
("Increase mez yield to 10%", 0.03),
("Add sfrxETH to Arbitrum", 0.02),
]
for i, (title, funds) in enumerate(proposals):
system.proposals[f"p{i}"] = Proposal(
id=f"p{i}", title=title, funds_requested=funds,
)
# Simulate staking & ticking
history = []
for epoch in range(100):
# Some voters stake/unstake
for vid, voter in system.voters.items():
if np.random.random() < 0.3:
candidates = [p for p in system.proposals.values() if p.status == "candidate"]
if candidates:
prop = np.random.choice(candidates)
amt = voter.holdings * np.random.uniform(0.05, 0.3)
stake(system, vid, prop.id, amt)
tick(system)
metrics = get_governance_metrics(system)
metrics["epoch"] = epoch
history.append(metrics)
_plot_conviction_progress(history, system)
def _plot_conviction_progress(history: list, system):
"""Plot conviction progress toward triggers for each proposal."""
fig = go.Figure()
for prop_id, prop in system.proposals.items():
progress = [
h["proposals"].get(prop_id, {}).get("progress", 0)
for h in history
]
epochs = [h["epoch"] for h in history]
fig.add_trace(go.Scatter(
x=epochs, y=progress,
name=f"{prop.title} ({'PASSED' if prop.status == 'passed' else prop.status})",
line=dict(width=2),
))
fig.add_hline(y=1.0, line_dash="dash", line_color="white",
annotation_text="Trigger threshold")
fig.update_layout(
title="Proposal Conviction Progress",
xaxis_title="Epoch",
yaxis_title="Progress (conviction / trigger)",
template="plotly_dark",
height=500,
)
st.plotly_chart(fig, use_container_width=True)
# Summary
for prop in system.proposals.values():
status_emoji = {"passed": "", "candidate": "", "failed": ""}.get(prop.status, "")
st.write(f"{status_emoji} **{prop.title}** — {prop.status} (age: {prop.age})")
def _render_trigger_map(beta: float, rho: float):
"""2D contour map of trigger function."""
from src.primitives.conviction import trigger_threshold, ConvictionParams
params = ConvictionParams(beta=beta, rho=rho)
supply_range = np.linspace(10_000, 1_000_000, 100)
share_range = np.linspace(0.001, beta - 0.001, 100)
Z = np.zeros((len(share_range), len(supply_range)))
for i, share in enumerate(share_range):
for j, supply in enumerate(supply_range):
Z[i, j] = np.log10(trigger_threshold(share, supply, params))
fig = go.Figure(go.Contour(
z=Z,
x=supply_range,
y=share_range,
colorscale="Viridis",
colorbar=dict(title="log₁₀(trigger)"),
))
fig.update_layout(
title="Trigger Function Map",
xaxis_title="Total Supply",
yaxis_title="Share of Funds Requested",
template="plotly_dark",
height=500,
)
st.plotly_chart(fig, use_container_width=True)
st.caption("Higher trigger = more conviction needed. Requesting closer to β makes it exponentially harder.")