myco-bonding-curve/notebooks/02_ellipsoid_surface.ipynb

282 lines
8.8 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# N-Dimensional Ellipsoid Bonding Surface\n",
"\n",
"Deep dive into the generalized N-asset bonding surface — the core pricing primitive for MYCO."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from mpl_toolkits.mplot3d import Axes3D\n",
"from matplotlib import cm\n",
"\n",
"%matplotlib inline\n",
"plt.rcParams['figure.figsize'] = (12, 8)\n",
"plt.rcParams['figure.dpi'] = 100"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from src.primitives.n_dimensional_surface import (\n",
" NDSurfaceParams, NDSurfaceState,\n",
" create_params, compute_invariant, compute_virtual_offsets,\n",
" mint, redeem, spot_prices,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2D Surface Visualization\n",
"\n",
"Start with 2 assets to visualize the invariant curve."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create 2D surface with different lambda values\n",
"fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n",
"\n",
"for ax, lambdas, title in [\n",
" (axes[0], np.array([1.0, 1.0]), 'λ = [1, 1] (circular)'),\n",
" (axes[1], np.array([3.0, 1.0]), 'λ = [3, 1] (elliptical)'),\n",
" (axes[2], np.array([5.0, 5.0]), 'λ = [5, 5] (concentrated)'),\n",
"]:\n",
" params = create_params(2, lambdas=lambdas)\n",
" \n",
" # Compute invariant at a reference point\n",
" ref_balances = np.array([1000.0, 1000.0])\n",
" r = compute_invariant(ref_balances, params)\n",
" offsets = compute_virtual_offsets(r, params)\n",
" \n",
" # Trace the invariant curve\n",
" b0_range = np.linspace(100, 2000, 500)\n",
" b1_values = []\n",
" for b0 in b0_range:\n",
" # Find b1 such that invariant = r\n",
" v0 = b0 + offsets[0]\n",
" A = params.A\n",
" # Quadratic in v1: sum of (A @ v)^2 terms involving v1\n",
" # Use numerical search\n",
" from scipy.optimize import brentq\n",
" def f(b1):\n",
" v = np.array([b0 + offsets[0], b1 + offsets[1]])\n",
" return np.sum((A @ v)**2) - r**2\n",
" try:\n",
" b1 = brentq(f, 1, 5000)\n",
" b1_values.append(b1)\n",
" except:\n",
" b1_values.append(np.nan)\n",
" \n",
" ax.plot(b0_range, b1_values, 'b-', linewidth=2)\n",
" ax.plot(1000, 1000, 'ro', markersize=8, label='Reference point')\n",
" ax.set_xlabel('Balance 0')\n",
" ax.set_ylabel('Balance 1')\n",
" ax.set_title(title)\n",
" ax.set_aspect('equal')\n",
" ax.legend()\n",
" ax.grid(True, alpha=0.3)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Minting Dynamics\n",
"\n",
"How token supply grows with deposits."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Simulate sequential deposits\n",
"params = create_params(3, lambdas=np.array([2.0, 2.0, 2.0]))\n",
"state = NDSurfaceState(\n",
" balances=np.array([1000.0, 1000.0, 1000.0]),\n",
" invariant=0.0,\n",
" supply=0.0,\n",
")\n",
"state.invariant = compute_invariant(state.balances, params)\n",
"state.supply = state.invariant\n",
"\n",
"deposits = []\n",
"supplies = [state.supply]\n",
"invariants = [state.invariant]\n",
"prices_history = [spot_prices(state, params)]\n",
"\n",
"for i in range(20):\n",
" # Deposit into different assets to create imbalance\n",
" deposit = np.zeros(3)\n",
" deposit[i % 3] = 200.0 # Rotate deposits\n",
" state, minted = mint(state, params, deposit)\n",
" deposits.append(deposit)\n",
" supplies.append(state.supply)\n",
" invariants.append(state.invariant)\n",
" prices_history.append(spot_prices(state, params))\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n",
"\n",
"axes[0].plot(supplies, 'b-o', markersize=4)\n",
"axes[0].set_xlabel('Deposit #')\n",
"axes[0].set_ylabel('Total supply')\n",
"axes[0].set_title('Supply Growth')\n",
"\n",
"axes[1].plot(invariants, 'r-o', markersize=4)\n",
"axes[1].set_xlabel('Deposit #')\n",
"axes[1].set_ylabel('Invariant r')\n",
"axes[1].set_title('Invariant Growth')\n",
"\n",
"prices_arr = np.array(prices_history)\n",
"for j in range(3):\n",
" axes[2].plot(prices_arr[:, j], '-o', markersize=4, label=f'Asset {j}')\n",
"axes[2].set_xlabel('Deposit #')\n",
"axes[2].set_ylabel('Spot price')\n",
"axes[2].set_title('Spot Prices (rotating deposits)')\n",
"axes[2].legend()\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3D Invariant Surface\n",
"\n",
"For 3 assets, the invariant defines an ellipsoidal surface in balance space."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"params = create_params(3, lambdas=np.array([2.0, 1.5, 1.0]))\n",
"ref = np.array([1000.0, 1000.0, 1000.0])\n",
"r = compute_invariant(ref, params)\n",
"offsets = compute_virtual_offsets(r, params)\n",
"\n",
"# Sample points on the ellipsoid using parametric form\n",
"fig = plt.figure(figsize=(10, 8))\n",
"ax = fig.add_subplot(111, projection='3d')\n",
"\n",
"# Use spherical coordinates to trace the surface\n",
"theta = np.linspace(0.1, np.pi/2 - 0.1, 30)\n",
"phi = np.linspace(0.1, np.pi/2 - 0.1, 30)\n",
"THETA, PHI = np.meshgrid(theta, phi)\n",
"\n",
"# Direction vectors on the positive octant\n",
"d0 = np.sin(THETA) * np.cos(PHI)\n",
"d1 = np.sin(THETA) * np.sin(PHI)\n",
"d2 = np.cos(THETA)\n",
"\n",
"A = params.A\n",
"B0 = np.zeros_like(THETA)\n",
"B1 = np.zeros_like(THETA)\n",
"B2 = np.zeros_like(THETA)\n",
"\n",
"for i in range(THETA.shape[0]):\n",
" for j in range(THETA.shape[1]):\n",
" d = np.array([d0[i,j], d1[i,j], d2[i,j]])\n",
" # Scale d so that |A @ (scale*d)|^2 = r^2\n",
" Ad = A @ d\n",
" scale = r / np.linalg.norm(Ad)\n",
" v = scale * d\n",
" b = v - offsets\n",
" B0[i,j] = max(b[0], 0)\n",
" B1[i,j] = max(b[1], 0)\n",
" B2[i,j] = max(b[2], 0)\n",
"\n",
"ax.plot_surface(B0, B1, B2, cmap=cm.viridis, alpha=0.6)\n",
"ax.scatter([1000], [1000], [1000], color='red', s=100, label='Reference')\n",
"ax.set_xlabel('Balance 0')\n",
"ax.set_ylabel('Balance 1')\n",
"ax.set_zlabel('Balance 2')\n",
"ax.set_title(f'3-Asset Ellipsoid Surface (λ = {list(params.lambdas)})')\n",
"ax.legend()\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Mint/Redeem Symmetry\n",
"\n",
"Verify that minting then redeeming the same amount returns approximately the original state."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"params = create_params(3)\n",
"initial_balances = np.array([1000.0, 1000.0, 1000.0])\n",
"state = NDSurfaceState(\n",
" balances=initial_balances.copy(),\n",
" invariant=compute_invariant(initial_balances, params),\n",
" supply=0.0,\n",
")\n",
"state.supply = state.invariant\n",
"\n",
"# Mint\n",
"deposit = np.array([100.0, 50.0, 75.0])\n",
"state_after_mint, minted = mint(state, params, deposit)\n",
"print(f'Minted: {minted:.4f} MYCO')\n",
"print(f'Balances after mint: {state_after_mint.balances}')\n",
"\n",
"# Redeem exactly what was minted\n",
"state_after_redeem, amounts_out = redeem(state_after_mint, params, minted)\n",
"print(f'\\nAmounts out: {amounts_out}')\n",
"print(f'Balances after redeem: {state_after_redeem.balances}')\n",
"print(f'\\nBalance difference from original: {state_after_redeem.balances - initial_balances}')\n",
"print(f'Supply difference: {state_after_redeem.supply - state.supply:.6f}')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}