TEC-analysis/notebooks/03_treasury_and_pools.ipynb

342 lines
13 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 03 — Treasury & Pool Analysis\n",
"\n",
"Analyzing the flow of value through the TEC's key pools:\n",
"- Common Pool (funding source for conviction voting)\n",
"- Reserve Pool (bonding curve collateral)\n",
"- Token balances and USD valuations over time\n",
"- Treasury health and sustainability metrics"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.dates as mdates\n",
"import seaborn as sns\n",
"\n",
"sns.set_theme(style='whitegrid', palette='deep')\n",
"plt.rcParams['figure.figsize'] = (14, 6)\n",
"plt.rcParams['figure.dpi'] = 100\n",
"\n",
"DATA = '../data/onchain'\n",
"\n",
"# Load pool data\n",
"common = pd.read_csv(f'{DATA}/dune_common_pool.csv')\n",
"reserve = pd.read_csv(f'{DATA}/dune_reserve_pool.csv')\n",
"balances = pd.read_csv(f'{DATA}/dune_token_balances_usd.csv')\n",
"\n",
"# Parse dates\n",
"common['date'] = pd.to_datetime(common['day'])\n",
"reserve['date'] = pd.to_datetime(reserve['day'])\n",
"balances['date'] = pd.to_datetime(balances['day'])\n",
"\n",
"print('Common Pool:', common['date'].min().date(), 'to', common['date'].max().date(), f'({len(common)} rows)')\n",
"print('Reserve Pool:', reserve['date'].min().date(), 'to', reserve['date'].max().date(), f'({len(reserve)} rows)')\n",
"print('Token Balances:', balances['date'].min().date(), 'to', balances['date'].max().date(), f'({len(balances)} rows)')\n",
"print('\\nToken symbols in balances:', balances['token_symbol'].unique())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Common Pool & Reserve Pool Over Time (Gnosis Chain era)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig, axes = plt.subplots(3, 1, figsize=(16, 14), sharex=True)\n",
"\n",
"# Common Pool balance\n",
"ax = axes[0]\n",
"ax.plot(common['date'], common['balance'], color='#2ecc71', linewidth=1.5)\n",
"ax.fill_between(common['date'], common['balance'], alpha=0.2, color='#2ecc71')\n",
"ax.set_ylabel('Balance (native token)')\n",
"ax.set_title('Common Pool Balance Over Time')\n",
"# Annotate key levels\n",
"ax.axhline(y=common['balance'].iloc[0], color='gray', linestyle='--', alpha=0.3)\n",
"ax.text(common['date'].iloc[0], common['balance'].iloc[0] * 1.02,\n",
" f'Start: {common[\"balance\"].iloc[0]:,.0f}', fontsize=9)\n",
"ax.text(common['date'].iloc[-1], common['balance'].iloc[-1] * 1.02,\n",
" f'End: {common[\"balance\"].iloc[-1]:,.0f}', fontsize=9)\n",
"\n",
"# Reserve Pool balance\n",
"ax = axes[1]\n",
"ax.plot(reserve['date'], reserve['balance'], color='#3498db', linewidth=1.5)\n",
"ax.fill_between(reserve['date'], reserve['balance'], alpha=0.2, color='#3498db')\n",
"ax.set_ylabel('Balance (native token)')\n",
"ax.set_title('Reserve Pool Balance Over Time')\n",
"ax.text(reserve['date'].iloc[0], reserve['balance'].iloc[0] * 1.02,\n",
" f'Start: {reserve[\"balance\"].iloc[0]:,.0f}', fontsize=9)\n",
"ax.text(reserve['date'].iloc[-1], reserve['balance'].iloc[-1] * 1.02,\n",
" f'End: {reserve[\"balance\"].iloc[-1]:,.0f}', fontsize=9)\n",
"\n",
"# Inflows & outflows for common pool\n",
"ax = axes[2]\n",
"ax.bar(common['date'], common['inflow'], color='#2ecc71', alpha=0.6, label='Inflow', width=1)\n",
"ax.bar(common['date'], -common['outflow'].abs(), color='#e74c3c', alpha=0.6, label='Outflow', width=1)\n",
"ax.set_ylabel('Flow Amount')\n",
"ax.set_title('Common Pool Daily Flows')\n",
"ax.legend()\n",
"ax.axhline(y=0, color='black', linewidth=0.5)\n",
"\n",
"for ax in axes:\n",
" ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
" ax.xaxis.set_major_locator(mdates.MonthLocator(interval=2))\n",
"\n",
"plt.setp(axes[-1].xaxis.get_majorticklabels(), rotation=45)\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/treasury_pools.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"# Key metrics\n",
"cp_drawdown = (common['balance'].iloc[-1] - common['balance'].max()) / common['balance'].max()\n",
"rp_drawdown = (reserve['balance'].iloc[-1] - reserve['balance'].max()) / reserve['balance'].max()\n",
"print(f'\\nCommon Pool: {common[\"balance\"].max():,.0f} peak → {common[\"balance\"].iloc[-1]:,.0f} end ({cp_drawdown:.1%} drawdown)')\n",
"print(f'Reserve Pool: {reserve[\"balance\"].max():,.0f} peak → {reserve[\"balance\"].iloc[-1]:,.0f} end ({rp_drawdown:.1%} drawdown)')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Post-Migration Token Balances (Optimism era)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Pivot balances by token\n",
"fig, axes = plt.subplots(2, 1, figsize=(16, 10))\n",
"\n",
"for token in balances['token_symbol'].unique():\n",
" subset = balances[balances['token_symbol'] == token].sort_values('date')\n",
" \n",
" # Native balance\n",
" axes[0].plot(subset['date'], subset['balance'], label=token, linewidth=1.5)\n",
" # USD balance\n",
" axes[1].plot(subset['date'], subset['balance_usd_f'], label=token, linewidth=1.5)\n",
"\n",
"axes[0].set_ylabel('Token Balance')\n",
"axes[0].set_title('Treasury Token Balances (Post-Migration)')\n",
"axes[0].legend()\n",
"\n",
"axes[1].set_ylabel('USD Value')\n",
"axes[1].set_title('Treasury USD Valuation')\n",
"axes[1].legend()\n",
"\n",
"for ax in axes:\n",
" ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
" ax.xaxis.set_major_locator(mdates.MonthLocator(interval=2))\n",
"\n",
"plt.setp(axes[-1].xaxis.get_majorticklabels(), rotation=45)\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/treasury_balances_usd.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"# Total USD over time\n",
"total_usd = balances.groupby('date')['balance_usd_f'].sum().sort_index()\n",
"print(f'\\nTotal treasury USD:')\n",
"print(f' Max: ${total_usd.max():,.0f}')\n",
"print(f' Min: ${total_usd.min():,.0f}')\n",
"print(f' Latest: ${total_usd.iloc[-1]:,.0f}')\n",
"print(f' Drawdown from max: {(total_usd.iloc[-1] - total_usd.max()) / total_usd.max():.1%}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Treasury Sustainability Analysis\n",
"\n",
"How long could the treasury sustain operations at various burn rates?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Calculate net flow rates for common pool\n",
"common_monthly = common.set_index('date').resample('ME').agg({\n",
" 'inflow': 'sum',\n",
" 'outflow': 'sum',\n",
" 'balance': 'last'\n",
"})\n",
"common_monthly['net_flow'] = common_monthly['inflow'] - common_monthly['outflow'].abs()\n",
"\n",
"fig, axes = plt.subplots(2, 1, figsize=(16, 10))\n",
"\n",
"# Monthly net flows\n",
"ax = axes[0]\n",
"colors = ['#2ecc71' if v >= 0 else '#e74c3c' for v in common_monthly['net_flow']]\n",
"ax.bar(common_monthly.index, common_monthly['net_flow'], color=colors, width=20)\n",
"ax.axhline(y=0, color='black', linewidth=0.5)\n",
"ax.set_ylabel('Net Flow')\n",
"ax.set_title('Common Pool Monthly Net Flows (positive = growing)')\n",
"ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
"\n",
"# Runway analysis\n",
"ax = axes[1]\n",
"# Monthly outflow rate\n",
"monthly_outflow = common_monthly['outflow'].abs()\n",
"ax.bar(common_monthly.index, monthly_outflow, color='#e74c3c', alpha=0.6, width=20)\n",
"ax.set_ylabel('Monthly Outflow')\n",
"ax.set_title('Common Pool Monthly Spending')\n",
"ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
"\n",
"# Add trend line\n",
"avg_outflow = monthly_outflow.mean()\n",
"ax.axhline(y=avg_outflow, color='red', linestyle='--', alpha=0.5,\n",
" label=f'Avg: {avg_outflow:,.0f}/month')\n",
"ax.legend()\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/treasury_sustainability.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"print(f'\\nSustainability metrics:')\n",
"print(f' Avg monthly outflow: {avg_outflow:,.0f}')\n",
"print(f' Avg monthly inflow: {common_monthly[\"inflow\"].mean():,.0f}')\n",
"print(f' Avg monthly net: {common_monthly[\"net_flow\"].mean():,.0f}')\n",
"print(f' Months with negative net flow: {(common_monthly[\"net_flow\"] < 0).sum()}/{len(common_monthly)}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. Reserve Ratio Analysis\n",
"\n",
"The ABC's reserve ratio determines the price curve shape. How did it evolve?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Reserve ratio = reserve_balance / (token_supply * price)\n",
"# We can approximate from pool data\n",
"# For now, show reserve pool vs common pool ratio\n",
"\n",
"# Align dates\n",
"common_daily = common.set_index('date')['balance'].resample('D').last().ffill()\n",
"reserve_daily = reserve.set_index('date')['balance'].resample('D').last().ffill()\n",
"\n",
"aligned = pd.DataFrame({\n",
" 'common_pool': common_daily,\n",
" 'reserve_pool': reserve_daily\n",
"}).dropna()\n",
"\n",
"aligned['total'] = aligned['common_pool'] + aligned['reserve_pool']\n",
"aligned['reserve_share'] = aligned['reserve_pool'] / aligned['total']\n",
"aligned['common_share'] = aligned['common_pool'] / aligned['total']\n",
"\n",
"fig, axes = plt.subplots(2, 1, figsize=(16, 10))\n",
"\n",
"ax = axes[0]\n",
"ax.stackplot(aligned.index,\n",
" aligned['reserve_pool'], aligned['common_pool'],\n",
" labels=['Reserve Pool', 'Common Pool'],\n",
" colors=['#3498db', '#2ecc71'], alpha=0.7)\n",
"ax.set_ylabel('Balance')\n",
"ax.set_title('Pool Composition Over Time')\n",
"ax.legend(loc='upper right')\n",
"\n",
"ax = axes[1]\n",
"ax.plot(aligned.index, aligned['reserve_share'], color='#3498db', linewidth=1.5, label='Reserve Share')\n",
"ax.plot(aligned.index, aligned['common_share'], color='#2ecc71', linewidth=1.5, label='Common Share')\n",
"ax.set_ylabel('Share of Total')\n",
"ax.set_title('Pool Share Ratio Over Time')\n",
"ax.legend()\n",
"ax.set_ylim(0, 1)\n",
"\n",
"for ax in axes:\n",
" ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
" ax.xaxis.set_major_locator(mdates.MonthLocator(interval=2))\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/pool_composition.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"print(f'\\nPool composition:')\n",
"print(f' Start - Reserve: {aligned[\"reserve_share\"].iloc[0]:.1%}, Common: {aligned[\"common_share\"].iloc[0]:.1%}')\n",
"print(f' End - Reserve: {aligned[\"reserve_share\"].iloc[-1]:.1%}, Common: {aligned[\"common_share\"].iloc[-1]:.1%}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Key Findings"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print('=' * 60)\n",
"print('TREASURY & POOLS — KEY FINDINGS')\n",
"print('=' * 60)\n",
"print(f'''\n",
"COMMON POOL:\n",
" Peak balance: {common[\"balance\"].max():,.0f}\n",
" Final balance: {common[\"balance\"].iloc[-1]:,.0f}\n",
" Total outflows: {common[\"outflow\"].abs().sum():,.0f}\n",
" Net negative months: {(common_monthly[\"net_flow\"] < 0).sum()}/{len(common_monthly)}\n",
"\n",
"RESERVE POOL:\n",
" Peak balance: {reserve[\"balance\"].max():,.0f}\n",
" Final balance: {reserve[\"balance\"].iloc[-1]:,.0f}\n",
"\n",
"POST-MIGRATION (Optimism):\n",
" Treasury peaked at: ${total_usd.max():,.0f}\n",
" Treasury at end: ${total_usd.iloc[-1]:,.0f}\n",
"\n",
"KEY QUESTIONS:\n",
" 1. Was the ABC tribute rate sufficient to replenish the common pool?\n",
" 2. How did the reserve ratio compare to the theoretical target?\n",
" 3. Did the treasury drawdown correlate with token price decline?\n",
" 4. Could different spending limits have extended the runway?\n",
"''')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}