From d57939f44e06a608f0ed4e59430e3ebda61638e5 Mon Sep 17 00:00:00 2001 From: zeroklaw <267418690+zeroklaw@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:11:05 +0000 Subject: [PATCH] feat: use USD/Bcycle for market reward inputs (closes #15) --- boundless_profitability.ipynb | 17 +++++++++-------- dashboard.py | 10 ++++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/boundless_profitability.ipynb b/boundless_profitability.ipynb index 16a8f00..4732d4b 100644 --- a/boundless_profitability.ipynb +++ b/boundless_profitability.ipynb @@ -57,11 +57,11 @@ "\n", "**This value will vary depending on the prover. To calculate the utilization for a specific prover, see [zerokrab/boundless-market-util](https://github.com/zerokrab/boundless-market-util).**\n", "\n", - "`MARKET_REWARD_USD_PER_MHZ`\n", + "`MARKET_REWARD_USD_PER_BCYCLE`\n", "\n", - "An array of potential market order prices (USD/Mhz) to be modelled.\n", + "An array of potential market order prices (USD/Bcycle) to be modelled.\n", "\n", - "Can be estimated from [market stats](https://explorer.boundless.network/stats), (see \"Lock price per cycle distribution\"). You can divide USD/Bcycle by 1000 to get USD/Mhz.\n", + "Can be estimated from [market stats](https://explorer.boundless.network/stats), (see \"Lock price per cycle distribution\").\n", "\n", "`FIXED_COST_MONTHLY_USD`\n", "\n", @@ -88,7 +88,7 @@ "MARKET_ORDER_UTIL = 0.5\n", "\n", "# Average reward per million cycles in USD (range of scenarios)\n", - "MARKET_REWARD_USD_PER_MHZ = [0.00003, 0.00005, 0.00007, 0.0001]\n", + "MARKET_REWARD_USD_PER_BCYCLE = [0.03, 0.05, 0.07, 0.1]\n", "\n", "# Fixed cost additional expenses\n", "FIXED_COST_MONTHLY_USD = 0\n", @@ -228,7 +228,7 @@ "source": [ "## 2. Compute Cost, Revenue, Profit\n", "\n", - "**Profit per epoch** = total revenue − total cost (GPU + fixed)." + "**Profit per epoch** = total revenue \u2212 total cost (GPU + fixed)." ] }, { @@ -243,8 +243,9 @@ " scenario = row[\"label\"]\n", " for zkc_price in ZKC_PRICES_USD:\n", " povw_revenue = mhz_per_epoch * POVW_ZKC_PER_MHZ_PER_EPOCH * zkc_price\n", - " for market_reward in MARKET_REWARD_USD_PER_MHZ:\n", - " market_revenue = mhz_per_epoch * market_reward * MARKET_ORDER_UTIL\n", + " for market_reward_bcycle in MARKET_REWARD_USD_PER_BCYCLE:\n", + " market_reward_mhz = market_reward_bcycle / 1000\n", + " market_revenue = mhz_per_epoch * market_reward_mhz * MARKET_ORDER_UTIL\n", " total_revenue = povw_revenue + market_revenue\n", " profit = total_revenue - total_cost_epoch\n", " results.append({\n", @@ -253,7 +254,7 @@ " \"num_gpus\": row[\"num_gpus\"],\n", " \"zkc_price_usd\": zkc_price,\n", " \"market_order_util\": MARKET_ORDER_UTIL,\n", - " \"market_reward_usd_per_mhz\": market_reward,\n", + " \"market_reward_usd_per_mhz\": market_reward_mhz,\n", " \"mhz_per_epoch\": mhz_per_epoch,\n", " \"cost_per_epoch\": total_cost_epoch,\n", " \"povw_revenue\": povw_revenue,\n", diff --git a/dashboard.py b/dashboard.py index 84c5093..0d84e27 100644 --- a/dashboard.py +++ b/dashboard.py @@ -42,8 +42,8 @@ def build_dashboard(df: pd.DataFrame | None = None) -> pn.viewable.Viewable: name="ZKC Price (USD)", options=zkc_prices, value=zkc_prices[len(zkc_prices) // 2] ) reward_slider = pn.widgets.FloatSlider( - name="Market Reward (USD/MHz)", start=0.00001, end=0.0002, step=0.00001, value=0.00007, - format="0.00000", + name="Market Reward (USD/Bcycle)", start=0.01, end=0.2, step=0.01, value=0.07, + format="0.00", ) # ── Tab 1: Profit Explorer ──────────────────────────────────────────────── @@ -53,7 +53,8 @@ def profit_chart(zkc_price, market_reward): # Base rows at the reference reward; scale market_revenue linearly to market_reward. # market_revenue ∝ reward (mhz * reward * util), so scaling is exact. sub = df[(df["zkc_price_usd"] == zkc_price) & (df["market_reward_usd_per_mhz"] == ref_reward)] - scale = market_reward / ref_reward if ref_reward > 0 else 0 + market_reward_mhz = market_reward / 1000 + scale = market_reward_mhz / ref_reward if ref_reward > 0 else 0 labels = sub["label"].tolist() costs = sub["cost_per_epoch"].tolist() @@ -96,7 +97,8 @@ def profit_chart(zkc_price, market_reward): @pn.depends(reward_slider) def breakeven_chart(market_reward): # Scale market_revenue to the chosen reward, then find min profitable ZKC price - scale = market_reward / ref_reward if ref_reward > 0 else 0 + market_reward_mhz = market_reward / 1000 + scale = market_reward_mhz / ref_reward if ref_reward > 0 else 0 sub = df[df["market_reward_usd_per_mhz"] == ref_reward].copy() sub = sub.assign( market_revenue_scaled=sub["market_revenue"] * scale,