feat: add System Config tab, presets, JSON export/import, and Dockerize for simulate.rspace.online
- Add System Config tab with ~15 parameter sliders grouped in accordions - Add 4 named presets (Conservative, Balanced, Aggressive, High-Risk) - Add JSON config export/import for sharing configurations - All tabs now read config from session_state instead of hardcoding - Signal Router uses config's fee/flow params as base AdaptiveParams - Multi-stage Dockerfile (Python 3.12-slim, non-root, healthcheck) - docker-compose.yml with Traefik labels for simulate.rspace.online Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1cb3701769
commit
d9dec8ba5b
|
|
@ -0,0 +1,9 @@
|
|||
.git
|
||||
tests/
|
||||
notebooks/
|
||||
docs/
|
||||
reference/
|
||||
*.pyc
|
||||
__pycache__
|
||||
.pytest_cache
|
||||
.claude/
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
FROM python:3.12-slim AS builder
|
||||
WORKDIR /build
|
||||
COPY pyproject.toml .
|
||||
COPY src/ src/
|
||||
COPY dashboard/ dashboard/
|
||||
RUN pip install --no-cache-dir ".[dashboard]"
|
||||
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
RUN useradd --create-home appuser
|
||||
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
||||
COPY --from=builder /usr/local/bin /usr/local/bin
|
||||
COPY src/ src/
|
||||
COPY dashboard/ dashboard/
|
||||
COPY pyproject.toml .
|
||||
RUN chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
EXPOSE 8501
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8501/_stcore/health')"
|
||||
ENTRYPOINT ["streamlit", "run", "dashboard/app.py", \
|
||||
"--server.port=8501", "--server.address=0.0.0.0", \
|
||||
"--server.headless=true", "--browser.gatherUsageStats=false"]
|
||||
|
|
@ -14,7 +14,13 @@ st.set_page_config(
|
|||
st.title("MYCO Bonding Surface Dashboard")
|
||||
st.caption("Interactive simulations for the multi-asset bonding curve with CRDT-native primitives.")
|
||||
|
||||
tab_launch, tab_dca, tab_signal, tab_stress, tab_crdt = st.tabs([
|
||||
# Initialize default config in session_state if not present
|
||||
if "myco_config" not in st.session_state:
|
||||
from src.composed.myco_surface import MycoSystemConfig
|
||||
st.session_state["myco_config"] = MycoSystemConfig()
|
||||
|
||||
tab_config, tab_launch, tab_dca, tab_signal, tab_stress, tab_crdt = st.tabs([
|
||||
"System Config",
|
||||
"Token Launch",
|
||||
"DCA Explorer",
|
||||
"Signal Router",
|
||||
|
|
@ -22,7 +28,10 @@ tab_launch, tab_dca, tab_signal, tab_stress, tab_crdt = st.tabs([
|
|||
"CRDT Flow",
|
||||
])
|
||||
|
||||
from dashboard.tabs import token_launch, dca_explorer, signal_router, stress_tests, crdt_flow
|
||||
from dashboard.tabs import system_config, token_launch, dca_explorer, signal_router, stress_tests, crdt_flow
|
||||
|
||||
with tab_config:
|
||||
system_config.render()
|
||||
|
||||
with tab_launch:
|
||||
token_launch.render()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
"""Named preset configurations for the MYCO system."""
|
||||
|
||||
PRESETS: dict[str, dict] = {
|
||||
"Conservative": {
|
||||
"n_reserve_assets": 3,
|
||||
"vault_names": ["USDC", "ETH", "DAI"],
|
||||
"surface_lambdas": [1.0, 1.0, 1.0],
|
||||
"initial_target_weights": [0.5, 0.25, 0.25],
|
||||
"pamm_alpha_bar": 5.0,
|
||||
"pamm_xu_bar": 0.9,
|
||||
"pamm_theta_bar": 0.7,
|
||||
"static_fee": 0.005,
|
||||
"surge_fee_rate": 0.02,
|
||||
"imbalance_threshold": 0.2,
|
||||
"flow_memory": 0.999,
|
||||
"flow_threshold": 0.05,
|
||||
"max_labor_mint_fraction": 0.05,
|
||||
"max_subscription_mint_fraction": 0.05,
|
||||
"max_staking_bonus_fraction": 0.03,
|
||||
},
|
||||
"Balanced": {
|
||||
"n_reserve_assets": 3,
|
||||
"vault_names": ["USDC", "ETH", "DAI"],
|
||||
"surface_lambdas": [1.0, 1.0, 1.0],
|
||||
"initial_target_weights": [0.34, 0.33, 0.33],
|
||||
"pamm_alpha_bar": 10.0,
|
||||
"pamm_xu_bar": 0.8,
|
||||
"pamm_theta_bar": 0.5,
|
||||
"static_fee": 0.003,
|
||||
"surge_fee_rate": 0.05,
|
||||
"imbalance_threshold": 0.2,
|
||||
"flow_memory": 0.999,
|
||||
"flow_threshold": 0.10,
|
||||
"max_labor_mint_fraction": 0.10,
|
||||
"max_subscription_mint_fraction": 0.10,
|
||||
"max_staking_bonus_fraction": 0.05,
|
||||
},
|
||||
"Aggressive": {
|
||||
"n_reserve_assets": 3,
|
||||
"vault_names": ["USDC", "ETH", "DAI"],
|
||||
"surface_lambdas": [1.0, 1.0, 1.0],
|
||||
"initial_target_weights": [0.34, 0.33, 0.33],
|
||||
"pamm_alpha_bar": 20.0,
|
||||
"pamm_xu_bar": 0.6,
|
||||
"pamm_theta_bar": 0.3,
|
||||
"static_fee": 0.001,
|
||||
"surge_fee_rate": 0.10,
|
||||
"imbalance_threshold": 0.2,
|
||||
"flow_memory": 0.999,
|
||||
"flow_threshold": 0.20,
|
||||
"max_labor_mint_fraction": 0.15,
|
||||
"max_subscription_mint_fraction": 0.15,
|
||||
"max_staking_bonus_fraction": 0.08,
|
||||
},
|
||||
"High-Risk": {
|
||||
"n_reserve_assets": 3,
|
||||
"vault_names": ["USDC", "ETH", "DAI"],
|
||||
"surface_lambdas": [1.0, 1.0, 1.0],
|
||||
"initial_target_weights": [0.34, 0.33, 0.33],
|
||||
"pamm_alpha_bar": 50.0,
|
||||
"pamm_xu_bar": 0.4,
|
||||
"pamm_theta_bar": 0.1,
|
||||
"static_fee": 0.0,
|
||||
"surge_fee_rate": 0.20,
|
||||
"imbalance_threshold": 0.2,
|
||||
"flow_memory": 0.999,
|
||||
"flow_threshold": 0.30,
|
||||
"max_labor_mint_fraction": 0.20,
|
||||
"max_subscription_mint_fraction": 0.20,
|
||||
"max_staking_bonus_fraction": 0.10,
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_PRESET = "Balanced"
|
||||
|
|
@ -28,6 +28,9 @@ def render():
|
|||
|
||||
|
||||
def _render_comparison():
|
||||
config = st.session_state.get("myco_config")
|
||||
n_assets = config.n_reserve_assets if config else 3
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
total = st.number_input("Total amount ($)", 1000, 100_000, 10_000, step=1000, key="dca_total")
|
||||
|
|
@ -39,6 +42,7 @@ def _render_comparison():
|
|||
if st.button("Compare Strategies", key="dca_compare_run"):
|
||||
with st.spinner("Running DCA comparison..."):
|
||||
results = scenario_dca_comparison(
|
||||
n_assets=n_assets,
|
||||
total_amount=float(total),
|
||||
n_chunks=chunks,
|
||||
interval=float(interval),
|
||||
|
|
@ -60,6 +64,8 @@ def _render_comparison():
|
|||
|
||||
|
||||
def _render_subscription():
|
||||
config = st.session_state.get("myco_config", MycoSystemConfig())
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
tier_payment = st.number_input("Payment/period ($)", 50, 5000, 100, step=50, key="sub_payment")
|
||||
|
|
@ -78,13 +84,12 @@ def _render_subscription():
|
|||
loyalty_halflife=90.0,
|
||||
),
|
||||
}
|
||||
config = SubscriptionDCAConfig(n_chunks=n_chunks, spread_fraction=0.8)
|
||||
sys_config = MycoSystemConfig(n_reserve_assets=3)
|
||||
dca_config = SubscriptionDCAConfig(n_chunks=n_chunks, spread_fraction=0.8)
|
||||
subscribers = [("subscriber_1", "custom", 0.0)]
|
||||
|
||||
with st.spinner("Running subscription DCA..."):
|
||||
sim = simulate_subscription_dca(
|
||||
tiers=tiers, config=config, system_config=sys_config,
|
||||
tiers=tiers, config=dca_config, system_config=config,
|
||||
subscribers=subscribers, duration=float(duration), dt=1.0,
|
||||
)
|
||||
st.session_state["sub_dca_result"] = sim
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ def render():
|
|||
st.header("Signal Router")
|
||||
st.caption("Parameters update live — no button needed (sub-ms computation).")
|
||||
|
||||
config = st.session_state.get("myco_config")
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
trajectory = st.selectbox(
|
||||
|
|
@ -39,20 +41,25 @@ def render():
|
|||
}
|
||||
prices = traj_map[trajectory]()
|
||||
|
||||
# Use config's fee/flow params as base adaptive params
|
||||
base_flow = config.flow_threshold if config else 0.1
|
||||
base_alpha = config.pamm_params.alpha_bar if config else 10.0
|
||||
base_surge = config.surge_fee_rate if config else 0.05
|
||||
|
||||
base = AdaptiveParams(
|
||||
flow_threshold=0.1,
|
||||
pamm_alpha_bar=10.0,
|
||||
surge_fee_rate=0.05,
|
||||
flow_threshold=base_flow,
|
||||
pamm_alpha_bar=base_alpha,
|
||||
surge_fee_rate=base_surge,
|
||||
oracle_multiplier_velocity=0.0,
|
||||
)
|
||||
config = SignalRouterConfig(
|
||||
router_config = SignalRouterConfig(
|
||||
k_vol_flow=k_vol_flow,
|
||||
k_dev_alpha=k_dev_alpha,
|
||||
k_vol_fee=k_vol_fee,
|
||||
k_oracle_vel=k_oracle_vel,
|
||||
)
|
||||
|
||||
result = simulate_signal_routing(base, config, prices)
|
||||
result = simulate_signal_routing(base, router_config, prices)
|
||||
result["prices"] = prices # Add for chart
|
||||
|
||||
st.plotly_chart(fig_signal_routing(result), use_container_width=True)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ from dashboard.charts import fig_bank_run_sweep
|
|||
def render():
|
||||
st.header("Stress Tests")
|
||||
|
||||
config = st.session_state.get("myco_config")
|
||||
n_assets = config.n_reserve_assets if config else 3
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
fractions_str = st.text_input(
|
||||
|
|
@ -20,6 +23,8 @@ def render():
|
|||
"Initial reserve ($)", 10_000, 1_000_000, 100_000, step=10_000,
|
||||
)
|
||||
|
||||
st.caption(f"Using **{n_assets}** reserve assets from System Config.")
|
||||
|
||||
if st.button("Run Stress Test", key="stress_run"):
|
||||
fractions = [float(f.strip()) for f in fractions_str.split(",")]
|
||||
results = {}
|
||||
|
|
@ -29,6 +34,7 @@ def render():
|
|||
with st.spinner(f"Running {frac:.0%} redemption..."):
|
||||
results[frac] = scenario_bank_run(
|
||||
initial_reserve=float(initial_reserve),
|
||||
n_assets=n_assets,
|
||||
redemption_fraction=frac,
|
||||
)
|
||||
progress.progress((i + 1) / len(fractions))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,367 @@
|
|||
"""System Config tab — parameter editor with presets and JSON import/export."""
|
||||
|
||||
import json
|
||||
|
||||
import numpy as np
|
||||
import streamlit as st
|
||||
|
||||
from src.composed.myco_surface import MycoSystemConfig
|
||||
from src.primitives.redemption_curve import PAMMParams
|
||||
from dashboard.presets import PRESETS, DEFAULT_PRESET
|
||||
|
||||
|
||||
def _apply_preset(name: str) -> None:
|
||||
"""Bulk-set session_state values from a preset."""
|
||||
preset = PRESETS[name]
|
||||
for key, value in preset.items():
|
||||
st.session_state[f"cfg_{key}"] = value
|
||||
st.session_state["cfg_active_preset"] = name
|
||||
|
||||
|
||||
def _build_config() -> MycoSystemConfig:
|
||||
"""Build a MycoSystemConfig from current session_state."""
|
||||
n = st.session_state.get("cfg_n_reserve_assets", 3)
|
||||
lambdas = st.session_state.get("cfg_surface_lambdas", [1.0] * n)
|
||||
weights = st.session_state.get("cfg_initial_target_weights", [1.0 / n] * n)
|
||||
vault_names = st.session_state.get("cfg_vault_names", ["USDC", "ETH", "DAI"][:n])
|
||||
|
||||
return MycoSystemConfig(
|
||||
n_reserve_assets=n,
|
||||
surface_lambdas=np.array(lambdas[:n]),
|
||||
vault_names=vault_names[:n],
|
||||
initial_target_weights=np.array(weights[:n]),
|
||||
pamm_params=PAMMParams(
|
||||
alpha_bar=st.session_state.get("cfg_pamm_alpha_bar", 10.0),
|
||||
xu_bar=st.session_state.get("cfg_pamm_xu_bar", 0.8),
|
||||
theta_bar=st.session_state.get("cfg_pamm_theta_bar", 0.5),
|
||||
),
|
||||
flow_memory=st.session_state.get("cfg_flow_memory", 0.999),
|
||||
flow_threshold=st.session_state.get("cfg_flow_threshold", 0.1),
|
||||
static_fee=st.session_state.get("cfg_static_fee", 0.003),
|
||||
surge_fee_rate=st.session_state.get("cfg_surge_fee_rate", 0.05),
|
||||
imbalance_threshold=st.session_state.get("cfg_imbalance_threshold", 0.2),
|
||||
max_labor_mint_fraction=st.session_state.get("cfg_max_labor_mint_fraction", 0.1),
|
||||
max_subscription_mint_fraction=st.session_state.get("cfg_max_subscription_mint_fraction", 0.1),
|
||||
max_staking_bonus_fraction=st.session_state.get("cfg_max_staking_bonus_fraction", 0.05),
|
||||
)
|
||||
|
||||
|
||||
def config_to_dict(config: MycoSystemConfig) -> dict:
|
||||
"""Serialize a MycoSystemConfig to a JSON-safe dict."""
|
||||
return {
|
||||
"n_reserve_assets": config.n_reserve_assets,
|
||||
"vault_names": config.vault_names,
|
||||
"surface_lambdas": config.surface_lambdas.tolist() if config.surface_lambdas is not None else None,
|
||||
"initial_target_weights": config.initial_target_weights.tolist() if config.initial_target_weights is not None else None,
|
||||
"pamm_alpha_bar": config.pamm_params.alpha_bar,
|
||||
"pamm_xu_bar": config.pamm_params.xu_bar,
|
||||
"pamm_theta_bar": config.pamm_params.theta_bar,
|
||||
"static_fee": config.static_fee,
|
||||
"surge_fee_rate": config.surge_fee_rate,
|
||||
"imbalance_threshold": config.imbalance_threshold,
|
||||
"flow_memory": config.flow_memory,
|
||||
"flow_threshold": config.flow_threshold,
|
||||
"max_labor_mint_fraction": config.max_labor_mint_fraction,
|
||||
"max_subscription_mint_fraction": config.max_subscription_mint_fraction,
|
||||
"max_staking_bonus_fraction": config.max_staking_bonus_fraction,
|
||||
}
|
||||
|
||||
|
||||
def dict_to_config(d: dict) -> MycoSystemConfig:
|
||||
"""Deserialize a dict into a MycoSystemConfig."""
|
||||
n = d.get("n_reserve_assets", 3)
|
||||
return MycoSystemConfig(
|
||||
n_reserve_assets=n,
|
||||
vault_names=d.get("vault_names", ["USDC", "ETH", "DAI"][:n]),
|
||||
surface_lambdas=np.array(d["surface_lambdas"]) if d.get("surface_lambdas") else None,
|
||||
initial_target_weights=np.array(d["initial_target_weights"]) if d.get("initial_target_weights") else None,
|
||||
pamm_params=PAMMParams(
|
||||
alpha_bar=d.get("pamm_alpha_bar", 10.0),
|
||||
xu_bar=d.get("pamm_xu_bar", 0.8),
|
||||
theta_bar=d.get("pamm_theta_bar", 0.5),
|
||||
),
|
||||
static_fee=d.get("static_fee", 0.003),
|
||||
surge_fee_rate=d.get("surge_fee_rate", 0.05),
|
||||
imbalance_threshold=d.get("imbalance_threshold", 0.2),
|
||||
flow_memory=d.get("flow_memory", 0.999),
|
||||
flow_threshold=d.get("flow_threshold", 0.1),
|
||||
max_labor_mint_fraction=d.get("max_labor_mint_fraction", 0.1),
|
||||
max_subscription_mint_fraction=d.get("max_subscription_mint_fraction", 0.1),
|
||||
max_staking_bonus_fraction=d.get("max_staking_bonus_fraction", 0.05),
|
||||
)
|
||||
|
||||
|
||||
def _load_from_dict(d: dict) -> None:
|
||||
"""Set session_state values from a config dict."""
|
||||
n = d.get("n_reserve_assets", 3)
|
||||
st.session_state["cfg_n_reserve_assets"] = n
|
||||
st.session_state["cfg_vault_names"] = d.get("vault_names", ["USDC", "ETH", "DAI"][:n])
|
||||
st.session_state["cfg_surface_lambdas"] = d.get("surface_lambdas", [1.0] * n)
|
||||
st.session_state["cfg_initial_target_weights"] = d.get("initial_target_weights", [1.0 / n] * n)
|
||||
st.session_state["cfg_pamm_alpha_bar"] = d.get("pamm_alpha_bar", 10.0)
|
||||
st.session_state["cfg_pamm_xu_bar"] = d.get("pamm_xu_bar", 0.8)
|
||||
st.session_state["cfg_pamm_theta_bar"] = d.get("pamm_theta_bar", 0.5)
|
||||
st.session_state["cfg_static_fee"] = d.get("static_fee", 0.003)
|
||||
st.session_state["cfg_surge_fee_rate"] = d.get("surge_fee_rate", 0.05)
|
||||
st.session_state["cfg_imbalance_threshold"] = d.get("imbalance_threshold", 0.2)
|
||||
st.session_state["cfg_flow_memory"] = d.get("flow_memory", 0.999)
|
||||
st.session_state["cfg_flow_threshold"] = d.get("flow_threshold", 0.1)
|
||||
st.session_state["cfg_max_labor_mint_fraction"] = d.get("max_labor_mint_fraction", 0.1)
|
||||
st.session_state["cfg_max_subscription_mint_fraction"] = d.get("max_subscription_mint_fraction", 0.1)
|
||||
st.session_state["cfg_max_staking_bonus_fraction"] = d.get("max_staking_bonus_fraction", 0.05)
|
||||
st.session_state["cfg_active_preset"] = "Custom"
|
||||
|
||||
|
||||
def _ensure_defaults():
|
||||
"""Initialize session_state with Balanced preset if not yet set."""
|
||||
if "cfg_n_reserve_assets" not in st.session_state:
|
||||
_apply_preset(DEFAULT_PRESET)
|
||||
|
||||
|
||||
def render():
|
||||
st.header("System Configuration")
|
||||
_ensure_defaults()
|
||||
|
||||
# --- Preset buttons ---
|
||||
st.subheader("Presets")
|
||||
cols = st.columns(len(PRESETS))
|
||||
for i, name in enumerate(PRESETS):
|
||||
with cols[i]:
|
||||
if st.button(name, key=f"preset_{name}", use_container_width=True):
|
||||
_apply_preset(name)
|
||||
st.rerun()
|
||||
|
||||
active = st.session_state.get("cfg_active_preset", DEFAULT_PRESET)
|
||||
st.caption(f"Active preset: **{active}**")
|
||||
|
||||
# --- Bonding Surface Geometry ---
|
||||
with st.expander("Bonding Surface Geometry", expanded=True):
|
||||
n = st.slider(
|
||||
"Reserve assets (n)",
|
||||
2, 6,
|
||||
value=st.session_state.get("cfg_n_reserve_assets", 3),
|
||||
key="w_n_assets",
|
||||
)
|
||||
# If n changed, reset per-asset lists
|
||||
prev_n = st.session_state.get("cfg_n_reserve_assets", 3)
|
||||
if n != prev_n:
|
||||
st.session_state["cfg_n_reserve_assets"] = n
|
||||
default_names = ["USDC", "ETH", "DAI", "WBTC", "SOL", "AVAX"]
|
||||
st.session_state["cfg_vault_names"] = default_names[:n]
|
||||
st.session_state["cfg_surface_lambdas"] = [1.0] * n
|
||||
st.session_state["cfg_initial_target_weights"] = [1.0 / n] * n
|
||||
st.session_state["cfg_active_preset"] = "Custom"
|
||||
st.rerun()
|
||||
|
||||
st.session_state["cfg_n_reserve_assets"] = n
|
||||
|
||||
# Vault names
|
||||
st.markdown("**Vault names**")
|
||||
vault_names = st.session_state.get("cfg_vault_names", ["USDC", "ETH", "DAI"][:n])
|
||||
name_cols = st.columns(n)
|
||||
new_names = []
|
||||
for i in range(n):
|
||||
with name_cols[i]:
|
||||
val = st.text_input(
|
||||
f"Asset {i+1}",
|
||||
value=vault_names[i] if i < len(vault_names) else f"Asset{i+1}",
|
||||
key=f"w_vault_{i}",
|
||||
)
|
||||
new_names.append(val)
|
||||
st.session_state["cfg_vault_names"] = new_names
|
||||
|
||||
# Surface lambdas
|
||||
st.markdown("**Surface lambdas**")
|
||||
lambdas = st.session_state.get("cfg_surface_lambdas", [1.0] * n)
|
||||
lam_cols = st.columns(n)
|
||||
new_lams = []
|
||||
for i in range(n):
|
||||
with lam_cols[i]:
|
||||
val = st.slider(
|
||||
f"\u03bb_{new_names[i]}",
|
||||
1.0, 10.0,
|
||||
value=float(lambdas[i]) if i < len(lambdas) else 1.0,
|
||||
step=0.5,
|
||||
key=f"w_lambda_{i}",
|
||||
)
|
||||
new_lams.append(val)
|
||||
st.session_state["cfg_surface_lambdas"] = new_lams
|
||||
|
||||
# Target weights
|
||||
st.markdown("**Initial target weights** (auto-normalized)")
|
||||
weights = st.session_state.get("cfg_initial_target_weights", [1.0 / n] * n)
|
||||
wt_cols = st.columns(n)
|
||||
raw_weights = []
|
||||
for i in range(n):
|
||||
with wt_cols[i]:
|
||||
val = st.slider(
|
||||
f"w_{new_names[i]}",
|
||||
0.01, 1.0,
|
||||
value=float(weights[i]) if i < len(weights) else 1.0 / n,
|
||||
step=0.01,
|
||||
key=f"w_weight_{i}",
|
||||
)
|
||||
raw_weights.append(val)
|
||||
total_w = sum(raw_weights)
|
||||
normalized = [w / total_w for w in raw_weights]
|
||||
st.session_state["cfg_initial_target_weights"] = normalized
|
||||
st.caption(f"Normalized: {', '.join(f'{w:.2%}' for w in normalized)}")
|
||||
|
||||
# --- P-AMM Redemption Curve ---
|
||||
with st.expander("P-AMM Redemption Curve"):
|
||||
c1, c2, c3 = st.columns(3)
|
||||
with c1:
|
||||
alpha = st.slider(
|
||||
"\u0101\u0304 (alpha_bar) — discount curvature",
|
||||
1.0, 100.0,
|
||||
value=st.session_state.get("cfg_pamm_alpha_bar", 10.0),
|
||||
step=1.0,
|
||||
key="w_alpha_bar",
|
||||
)
|
||||
st.session_state["cfg_pamm_alpha_bar"] = alpha
|
||||
with c2:
|
||||
xu = st.slider(
|
||||
"x\u0304_U (xu_bar) — no-discount threshold",
|
||||
0.0, 1.0,
|
||||
value=st.session_state.get("cfg_pamm_xu_bar", 0.8),
|
||||
step=0.05,
|
||||
key="w_xu_bar",
|
||||
)
|
||||
st.session_state["cfg_pamm_xu_bar"] = xu
|
||||
with c3:
|
||||
theta = st.slider(
|
||||
"\u03b8\u0304 (theta_bar) — floor redemption rate",
|
||||
0.0, 1.0,
|
||||
value=st.session_state.get("cfg_pamm_theta_bar", 0.5),
|
||||
step=0.05,
|
||||
key="w_theta_bar",
|
||||
)
|
||||
st.session_state["cfg_pamm_theta_bar"] = theta
|
||||
|
||||
# --- Fees & Flow Dampening ---
|
||||
with st.expander("Fees & Flow Dampening"):
|
||||
c1, c2 = st.columns(2)
|
||||
with c1:
|
||||
sf = st.slider(
|
||||
"static_fee",
|
||||
0.0, 0.05,
|
||||
value=st.session_state.get("cfg_static_fee", 0.003),
|
||||
step=0.001,
|
||||
format="%.3f",
|
||||
key="w_static_fee",
|
||||
)
|
||||
st.session_state["cfg_static_fee"] = sf
|
||||
|
||||
sr = st.slider(
|
||||
"surge_fee_rate",
|
||||
0.0, 0.5,
|
||||
value=st.session_state.get("cfg_surge_fee_rate", 0.05),
|
||||
step=0.01,
|
||||
key="w_surge_fee",
|
||||
)
|
||||
st.session_state["cfg_surge_fee_rate"] = sr
|
||||
|
||||
it = st.slider(
|
||||
"imbalance_threshold",
|
||||
0.0, 0.5,
|
||||
value=st.session_state.get("cfg_imbalance_threshold", 0.2),
|
||||
step=0.05,
|
||||
key="w_imbalance_thresh",
|
||||
)
|
||||
st.session_state["cfg_imbalance_threshold"] = it
|
||||
|
||||
with c2:
|
||||
fm = st.slider(
|
||||
"flow_memory",
|
||||
0.9, 0.9999,
|
||||
value=st.session_state.get("cfg_flow_memory", 0.999),
|
||||
step=0.0001,
|
||||
format="%.4f",
|
||||
key="w_flow_memory",
|
||||
)
|
||||
st.session_state["cfg_flow_memory"] = fm
|
||||
|
||||
ft = st.slider(
|
||||
"flow_threshold",
|
||||
0.01, 0.5,
|
||||
value=st.session_state.get("cfg_flow_threshold", 0.1),
|
||||
step=0.01,
|
||||
key="w_flow_threshold",
|
||||
)
|
||||
st.session_state["cfg_flow_threshold"] = ft
|
||||
|
||||
# --- Commitment Caps ---
|
||||
with st.expander("Commitment Caps"):
|
||||
c1, c2, c3 = st.columns(3)
|
||||
with c1:
|
||||
lm = st.slider(
|
||||
"max_labor_mint_fraction",
|
||||
0.0, 0.3,
|
||||
value=st.session_state.get("cfg_max_labor_mint_fraction", 0.1),
|
||||
step=0.01,
|
||||
key="w_labor_cap",
|
||||
)
|
||||
st.session_state["cfg_max_labor_mint_fraction"] = lm
|
||||
with c2:
|
||||
sm = st.slider(
|
||||
"max_subscription_mint_fraction",
|
||||
0.0, 0.3,
|
||||
value=st.session_state.get("cfg_max_subscription_mint_fraction", 0.1),
|
||||
step=0.01,
|
||||
key="w_sub_cap",
|
||||
)
|
||||
st.session_state["cfg_max_subscription_mint_fraction"] = sm
|
||||
with c3:
|
||||
sb = st.slider(
|
||||
"max_staking_bonus_fraction",
|
||||
0.0, 0.15,
|
||||
value=st.session_state.get("cfg_max_staking_bonus_fraction", 0.05),
|
||||
step=0.01,
|
||||
key="w_staking_cap",
|
||||
)
|
||||
st.session_state["cfg_max_staking_bonus_fraction"] = sb
|
||||
|
||||
# --- Build and store config ---
|
||||
config = _build_config()
|
||||
st.session_state["myco_config"] = config
|
||||
|
||||
# --- JSON Export / Import ---
|
||||
st.divider()
|
||||
exp_col, imp_col = st.columns(2)
|
||||
with exp_col:
|
||||
st.subheader("Export Config")
|
||||
config_json = json.dumps(config_to_dict(config), indent=2)
|
||||
st.download_button(
|
||||
"Download JSON",
|
||||
data=config_json,
|
||||
file_name="myco_config.json",
|
||||
mime="application/json",
|
||||
)
|
||||
with imp_col:
|
||||
st.subheader("Import Config")
|
||||
uploaded = st.file_uploader("Upload JSON", type=["json"], key="cfg_upload")
|
||||
if uploaded is not None:
|
||||
try:
|
||||
d = json.loads(uploaded.read())
|
||||
_load_from_dict(d)
|
||||
st.success("Config loaded successfully!")
|
||||
st.rerun()
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
st.error(f"Invalid config file: {e}")
|
||||
|
||||
# --- Config Summary ---
|
||||
st.divider()
|
||||
st.subheader("Active Configuration Summary")
|
||||
n = config.n_reserve_assets
|
||||
names = ", ".join(config.vault_names[:n])
|
||||
st.markdown(
|
||||
f"**{n} assets** ({names}) | "
|
||||
f"P-AMM: \u0101={config.pamm_params.alpha_bar:.1f}, "
|
||||
f"x\u0304_U={config.pamm_params.xu_bar:.2f}, "
|
||||
f"\u03b8\u0304={config.pamm_params.theta_bar:.2f} | "
|
||||
f"Fees: static={config.static_fee:.3f}, surge={config.surge_fee_rate:.2f} | "
|
||||
f"Flow: mem={config.flow_memory:.4f}, thresh={config.flow_threshold:.2f} | "
|
||||
f"Caps: labor={config.max_labor_mint_fraction:.0%}, "
|
||||
f"sub={config.max_subscription_mint_fraction:.0%}, "
|
||||
f"stake={config.max_staking_bonus_fraction:.0%}"
|
||||
)
|
||||
|
|
@ -9,16 +9,19 @@ from dashboard.charts import fig_simulation_overview
|
|||
def render():
|
||||
st.header("Token Launch Simulation")
|
||||
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
config = st.session_state.get("myco_config")
|
||||
n_assets = config.n_reserve_assets if config else 3
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
n_assets = st.slider("Reserve assets", 2, 6, 3)
|
||||
with col2:
|
||||
n_depositors = st.slider("Depositors", 10, 200, 50)
|
||||
with col3:
|
||||
with col2:
|
||||
total_raise = st.number_input("Total raise ($)", 10_000, 1_000_000, 100_000, step=10_000)
|
||||
with col4:
|
||||
with col3:
|
||||
duration = st.slider("Duration (days)", 30, 365, 90)
|
||||
|
||||
st.caption(f"Using **{n_assets}** reserve assets from System Config.")
|
||||
|
||||
if st.button("Run Simulation", key="token_launch_run"):
|
||||
with st.spinner("Running token launch simulation..."):
|
||||
result = scenario_token_launch(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
services:
|
||||
myco-dashboard:
|
||||
build: .
|
||||
container_name: myco-dashboard
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.myco-dashboard.rule=Host(`simulate.rspace.online`)"
|
||||
- "traefik.http.routers.myco-dashboard.entrypoints=web"
|
||||
- "traefik.http.services.myco-dashboard.loadbalancer.server.port=8501"
|
||||
- "traefik.http.services.myco-dashboard.loadbalancer.healthcheck.path=/_stcore/health"
|
||||
- "traefik.http.services.myco-dashboard.loadbalancer.healthcheck.interval=30s"
|
||||
networks:
|
||||
- traefik-public
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
reservations:
|
||||
memory: 512M
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
Loading…
Reference in New Issue