Add ABC & market analysis notebook, all Dune data downloaded

All 18 Dune queries now downloaded (98K+ total rows):
- ABC trades: 2,615 detailed + 32K raw (price, reserve, supply per txn)
- DEX trades: 26,107 (Honeyswap + Velodrome), with buys/sells split
- ABC tributes: monthly buy/sell tribute distribution
- Holders: 723 ranked with concentration curve, daily holder changes
- Trade summaries: monthly action breakdowns

Notebook 05 analyzes:
- ABC price history and reserve ratio evolution
- Buy/sell asymmetry (5.6:1 sell:buy ratio)
- Tribute revenue (insufficient to sustain common pool)
- ABC vs DEX price divergence and spread
- Token holder concentration (top holder: gideonro.eth at 13.2%)
- DEX volume and liquidity across Honeyswap/Velodrome

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-03 13:19:29 -07:00
parent 0dc3c41111
commit 0698ced6fe
1 changed files with 585 additions and 0 deletions

View File

@ -0,0 +1,585 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 05 — Augmented Bonding Curve & Market Analysis\n",
"\n",
"The core questions:\n",
"1. How did the ABC price track vs secondary market (Honeyswap/Velodrome)?\n",
"2. Was the buy/sell ratio sustainable? (Spoiler: 2,217 sells vs 398 buys)\n",
"3. How much tribute revenue did the ABC generate?\n",
"4. How concentrated was token ownership?\n",
"5. Did price discovery happen on the ABC or the DEX?"
]
},
{
"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 datasets\n",
"abc_trades = pd.read_csv(f'{DATA}/dune_q2994918.csv') # ABC with reserve/supply\n",
"abc_all = pd.read_csv(f'{DATA}/dune_q3743980.csv') # All ABC trades\n",
"dex_all = pd.read_csv(f'{DATA}/dune_q3743672.csv') # All DEX trades\n",
"dex_buys = pd.read_csv(f'{DATA}/dune_q3743698.csv') # DEX buys\n",
"dex_sells = pd.read_csv(f'{DATA}/dune_q3743704.csv') # DEX sells\n",
"tributes_a = pd.read_csv(f'{DATA}/dune_q3744004.csv') # Tributes monthly\n",
"tributes_b = pd.read_csv(f'{DATA}/dune_q3744008.csv') # Tributes monthly alt\n",
"holders = pd.read_csv(f'{DATA}/dune_q3743746.csv') # Top holders\n",
"holder_hist = pd.read_csv(f'{DATA}/dune_q3743731.csv') # Holder count over time\n",
"supply = pd.read_csv(f'{DATA}/dune_q3743718.csv') # Holders & supply\n",
"trade_summary = pd.read_csv(f'{DATA}/dune_q3743710.csv') # Trade action summary\n",
"\n",
"# Parse dates\n",
"abc_trades['date'] = pd.to_datetime(abc_trades['block_time'])\n",
"abc_all['date'] = pd.to_datetime(abc_all['block_time'])\n",
"dex_all['date'] = pd.to_datetime(dex_all['block_time'])\n",
"\n",
"print(f'ABC trades (detailed): {len(abc_trades)} rows, {abc_trades[\"date\"].min().date()} to {abc_trades[\"date\"].max().date()}')\n",
"print(f'ABC trades (all): {len(abc_all)} rows')\n",
"print(f'DEX trades: {len(dex_all)} rows, {dex_all[\"date\"].min().date()} to {dex_all[\"date\"].max().date()}')\n",
"print(f' Projects: {dex_all[\"project\"].unique()}')\n",
"print(f'ABC action counts: {abc_trades[\"action\"].value_counts().to_dict()}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. ABC Price History & Reserve Ratio"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"abc_sorted = abc_trades.sort_values('date').copy()\n",
"\n",
"fig, axes = plt.subplots(3, 1, figsize=(16, 14), sharex=True)\n",
"\n",
"# Price over time colored by action\n",
"ax = axes[0]\n",
"buys = abc_sorted[abc_sorted['action'] == 'Buy']\n",
"sells = abc_sorted[abc_sorted['action'] == 'Sell']\n",
"ax.scatter(buys['date'], buys['price_per_token'], c='#2ecc71', s=15, alpha=0.5, label=f'Buy ({len(buys)})')\n",
"ax.scatter(sells['date'], sells['price_per_token'], c='#e74c3c', s=15, alpha=0.5, label=f'Sell ({len(sells)})')\n",
"ax.set_ylabel('Price (xDAI per TEC)')\n",
"ax.set_title('ABC Price Over Time')\n",
"ax.legend()\n",
"\n",
"# Reserve balance\n",
"ax = axes[1]\n",
"ax.plot(abc_sorted['date'], abc_sorted['reserve_balance'], color='#3498db', linewidth=1)\n",
"ax.fill_between(abc_sorted['date'], abc_sorted['reserve_balance'], alpha=0.2, color='#3498db')\n",
"ax.set_ylabel('Reserve Balance (xDAI)')\n",
"ax.set_title('ABC Reserve Pool Over Time')\n",
"\n",
"# Supply\n",
"ax = axes[2]\n",
"ax.plot(abc_sorted['date'], abc_sorted['cumulative_supply'], color='teal', linewidth=1)\n",
"ax.fill_between(abc_sorted['date'], abc_sorted['cumulative_supply'], alpha=0.2, color='teal')\n",
"ax.set_ylabel('Token Supply')\n",
"ax.set_title('TEC Circulating Supply')\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=3))\n",
"\n",
"plt.setp(axes[-1].xaxis.get_majorticklabels(), rotation=45)\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/abc_price_reserve.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"print(f'\\nABC Price Stats:')\n",
"print(f' Peak: {abc_sorted[\"price_per_token\"].max():.4f} xDAI')\n",
"print(f' Trough: {abc_sorted[\"price_per_token\"].min():.4f} xDAI')\n",
"print(f' Drawdown: {(abc_sorted[\"price_per_token\"].min() / abc_sorted[\"price_per_token\"].max() - 1):.1%}')\n",
"print(f'\\nReserve:')\n",
"print(f' Peak: {abc_sorted[\"reserve_balance\"].max():,.0f} xDAI')\n",
"print(f' Final: {abc_sorted[\"reserve_balance\"].iloc[-1]:,.0f} xDAI')\n",
"print(f'\\nSupply:')\n",
"print(f' Peak: {abc_sorted[\"cumulative_supply\"].max():,.0f} TEC')\n",
"print(f' Final: {abc_sorted[\"cumulative_supply\"].iloc[-1]:,.0f} TEC')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Buy/Sell Asymmetry — The Structural Pressure"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Monthly buy vs sell volume\n",
"abc_monthly = abc_sorted.copy()\n",
"abc_monthly['month'] = abc_monthly['date'].dt.to_period('M').dt.to_timestamp()\n",
"\n",
"monthly_agg = abc_monthly.groupby(['month', 'action']).agg(\n",
" count=('Amount', 'count'),\n",
" total_amount=('Amount', 'sum'),\n",
" total_tokens=('noToken', 'sum'),\n",
" avg_price=('price_per_token', 'mean'),\n",
").reset_index()\n",
"\n",
"fig, axes = plt.subplots(2, 2, figsize=(18, 10))\n",
"\n",
"# Transaction count by month\n",
"ax = axes[0, 0]\n",
"for action, color in [('Buy', '#2ecc71'), ('Sell', '#e74c3c')]:\n",
" subset = monthly_agg[monthly_agg['action'] == action]\n",
" ax.bar(subset['month'] + pd.Timedelta(days=8 if action == 'Sell' else -8),\n",
" subset['count'], width=15, color=color, alpha=0.7, label=action)\n",
"ax.set_ylabel('Transaction Count')\n",
"ax.set_title('Monthly ABC Transaction Count')\n",
"ax.legend()\n",
"\n",
"# Volume by month (xDAI amount)\n",
"ax = axes[0, 1]\n",
"for action, color in [('Buy', '#2ecc71'), ('Sell', '#e74c3c')]:\n",
" subset = monthly_agg[monthly_agg['action'] == action]\n",
" ax.bar(subset['month'] + pd.Timedelta(days=8 if action == 'Sell' else -8),\n",
" subset['total_amount'], width=15, color=color, alpha=0.7, label=action)\n",
"ax.set_ylabel('Volume (xDAI)')\n",
"ax.set_title('Monthly ABC Volume')\n",
"ax.legend()\n",
"\n",
"# Buy/sell ratio over time\n",
"ax = axes[1, 0]\n",
"buy_monthly = monthly_agg[monthly_agg['action'] == 'Buy'].set_index('month')['count']\n",
"sell_monthly = monthly_agg[monthly_agg['action'] == 'Sell'].set_index('month')['count']\n",
"ratio = (buy_monthly / sell_monthly).dropna()\n",
"colors = ['#2ecc71' if r >= 1 else '#e74c3c' for r in ratio]\n",
"ax.bar(ratio.index, ratio.values, width=20, color=colors, alpha=0.7)\n",
"ax.axhline(y=1, color='black', linestyle='--', alpha=0.5, label='Parity')\n",
"ax.set_ylabel('Buy/Sell Ratio')\n",
"ax.set_title('Monthly Buy/Sell Ratio (<1 = net selling pressure)')\n",
"ax.legend()\n",
"\n",
"# Cumulative net flow\n",
"ax = axes[1, 1]\n",
"abc_sorted_copy = abc_sorted.copy()\n",
"abc_sorted_copy['signed_amount'] = abc_sorted_copy.apply(\n",
" lambda r: r['Amount'] if r['action'] == 'Buy' else -r['Amount'], axis=1\n",
")\n",
"abc_sorted_copy['cum_flow'] = abc_sorted_copy['signed_amount'].cumsum()\n",
"ax.plot(abc_sorted_copy['date'], abc_sorted_copy['cum_flow'], color='steelblue', linewidth=1.5)\n",
"ax.fill_between(abc_sorted_copy['date'], abc_sorted_copy['cum_flow'],\n",
" where=abc_sorted_copy['cum_flow'] >= 0, color='#2ecc71', alpha=0.2)\n",
"ax.fill_between(abc_sorted_copy['date'], abc_sorted_copy['cum_flow'],\n",
" where=abc_sorted_copy['cum_flow'] < 0, color='#e74c3c', alpha=0.2)\n",
"ax.axhline(y=0, color='black', linewidth=0.5)\n",
"ax.set_ylabel('Cumulative Net Flow (xDAI)')\n",
"ax.set_title('Cumulative Buy - Sell Flow (negative = capital leaving)')\n",
"\n",
"for row in axes:\n",
" for ax in row:\n",
" ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/abc_buysell.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"total_buy = abc_sorted[abc_sorted['action'] == 'Buy']['Amount'].sum()\n",
"total_sell = abc_sorted[abc_sorted['action'] == 'Sell']['Amount'].sum()\n",
"print(f'\\nBuy/Sell Analysis:')\n",
"print(f' Total buys: {len(buys)} txns, {total_buy:,.0f} xDAI')\n",
"print(f' Total sells: {len(sells)} txns, {total_sell:,.0f} xDAI')\n",
"print(f' Sell:Buy ratio (count): {len(sells)/len(buys):.1f}:1')\n",
"print(f' Sell:Buy ratio (volume): {total_sell/total_buy:.1f}:1')\n",
"print(f' Net outflow: {total_sell - total_buy:,.0f} xDAI')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. ABC Tribute Revenue"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Tribute from individual trades\n",
"abc_sorted['tribute_val'] = pd.to_numeric(abc_sorted['tribute'], errors='coerce')\n",
"tribute_by_action = abc_sorted.groupby('action')['tribute_val'].sum()\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(16, 6))\n",
"\n",
"# Cumulative tribute\n",
"ax = axes[0]\n",
"for action, color in [('Buy', '#2ecc71'), ('Sell', '#e74c3c')]:\n",
" subset = abc_sorted[abc_sorted['action'] == action].copy()\n",
" subset['cum_tribute'] = subset['tribute_val'].cumsum()\n",
" ax.plot(subset['date'], subset['cum_tribute'], color=color, linewidth=1.5, label=f'{action} tribute')\n",
"ax.set_ylabel('Cumulative Tribute (xDAI)')\n",
"ax.set_title('ABC Tribute Revenue Over Time')\n",
"ax.legend()\n",
"\n",
"# Monthly tribute from aggregated data\n",
"ax = axes[1]\n",
"# Use the detailed tribute tables\n",
"for df, label in [(tributes_a, 'Post-migration'), (tributes_b, 'Alt')]:\n",
" df['date'] = pd.to_datetime(df['date'])\n",
"\n",
"t_pivot = tributes_a.pivot_table(index='date', columns='action', values='Tribute_Distribution', aggfunc='sum').fillna(0)\n",
"if 'Buy' in t_pivot.columns:\n",
" ax.bar(t_pivot.index, t_pivot.get('Buy', 0), width=20, color='#2ecc71', alpha=0.7, label='Buy tribute')\n",
"if 'Sell' in t_pivot.columns:\n",
" ax.bar(t_pivot.index, t_pivot.get('Sell', 0), width=20, color='#e74c3c', alpha=0.7,\n",
" bottom=t_pivot.get('Buy', 0), label='Sell tribute')\n",
"ax.set_ylabel('Tribute (xDAI)')\n",
"ax.set_title('Monthly ABC Tribute Revenue')\n",
"ax.legend()\n",
"\n",
"for ax in axes:\n",
" ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/abc_tributes.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"print(f'\\nTribute Revenue:')\n",
"for action in ['Buy', 'Sell']:\n",
" t = tribute_by_action.get(action, 0)\n",
" print(f' {action} tribute: {t:,.2f} xDAI')\n",
"print(f' Total: {tribute_by_action.sum():,.2f} xDAI')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. Primary (ABC) vs Secondary (DEX) Market\n",
"\n",
"Where did price discovery happen? Did the ABC price diverge from the DEX price?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Extract price from DEX trades\n",
"# DEX buys and sells have price_per_token column\n",
"dex_with_price = pd.concat([\n",
" dex_buys[['block_time', 'project', 'action', 'price_per_token', 'Amount']].copy(),\n",
" dex_sells[['block_time', 'project', 'action', 'price_per_token', 'Amount']].copy(),\n",
"])\n",
"dex_with_price['date'] = pd.to_datetime(dex_with_price['block_time'])\n",
"dex_with_price['price_per_token'] = pd.to_numeric(dex_with_price['price_per_token'], errors='coerce')\n",
"dex_with_price = dex_with_price.dropna(subset=['price_per_token'])\n",
"dex_with_price = dex_with_price[dex_with_price['price_per_token'] > 0]\n",
"\n",
"# Daily average price from each venue\n",
"abc_daily_price = abc_sorted.set_index('date')['price_per_token'].resample('D').mean().dropna()\n",
"dex_daily_price = dex_with_price.set_index('date')['price_per_token'].resample('D').mean().dropna()\n",
"\n",
"fig, axes = plt.subplots(2, 1, figsize=(16, 10))\n",
"\n",
"# Price comparison\n",
"ax = axes[0]\n",
"ax.plot(abc_daily_price.index, abc_daily_price.values, label='ABC Price', color='steelblue', linewidth=1, alpha=0.8)\n",
"ax.plot(dex_daily_price.index, dex_daily_price.values, label='DEX Price', color='coral', linewidth=1, alpha=0.8)\n",
"ax.set_ylabel('Price (xDAI per TEC)')\n",
"ax.set_title('ABC vs DEX Price — Where did price discovery happen?')\n",
"ax.legend()\n",
"\n",
"# Price spread/premium\n",
"ax = axes[1]\n",
"aligned_prices = pd.DataFrame({\n",
" 'abc': abc_daily_price,\n",
" 'dex': dex_daily_price\n",
"}).dropna()\n",
"if len(aligned_prices) > 0:\n",
" spread = (aligned_prices['dex'] - aligned_prices['abc']) / aligned_prices['abc'] * 100\n",
" ax.fill_between(spread.index, spread, where=spread >= 0, color='#2ecc71', alpha=0.3, label='DEX premium')\n",
" ax.fill_between(spread.index, spread, where=spread < 0, color='#e74c3c', alpha=0.3, label='DEX discount')\n",
" ax.plot(spread.index, spread, color='black', linewidth=0.5)\n",
" ax.axhline(y=0, color='black', linewidth=0.5)\n",
" ax.set_ylabel('DEX Premium/Discount (%)')\n",
" ax.set_title('DEX Price Relative to ABC (+ = DEX more expensive)')\n",
" ax.legend()\n",
" \n",
" print(f'\\nPrice Spread Stats:')\n",
" print(f' Mean spread: {spread.mean():.1f}%')\n",
" print(f' Median spread: {spread.median():.1f}%')\n",
" print(f' Max DEX premium: {spread.max():.1f}%')\n",
" print(f' Max DEX discount: {spread.min():.1f}%')\n",
" print(f' Days with DEX discount: {(spread < 0).sum()}/{len(spread)}')\n",
"else:\n",
" ax.text(0.5, 0.5, 'Insufficient overlapping price data', transform=ax.transAxes, ha='center')\n",
"\n",
"for ax in axes:\n",
" ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/abc_vs_dex.png', dpi=150, bbox_inches='tight')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Token Holder Concentration"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Clean holders data (remove HTML from address column)\n",
"holders_clean = holders.copy()\n",
"holders_clean['addr'] = holders_clean['address_raw']\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(16, 7))\n",
"\n",
"# Top 20 holders\n",
"ax = axes[0]\n",
"top20 = holders_clean.head(20)\n",
"labels = top20.apply(\n",
" lambda r: r['ens_name'] if pd.notna(r['ens_name']) and len(str(r['ens_name'])) < 20\n",
" else f\"{str(r['addr'])[:8]}...\", axis=1\n",
")\n",
"ax.barh(range(len(top20)), top20['balance'], color='steelblue')\n",
"ax.set_yticks(range(len(top20)))\n",
"ax.set_yticklabels(labels, fontsize=8)\n",
"ax.invert_yaxis()\n",
"ax.set_xlabel('TEC Balance')\n",
"ax.set_title(f'Top 20 Token Holders')\n",
"\n",
"# Cumulative concentration\n",
"ax = axes[1]\n",
"ax.plot(holders_clean['rank_number'], holders_clean['cumulative_perc'] * 100,\n",
" color='coral', linewidth=2)\n",
"ax.axhline(y=50, color='gray', linestyle='--', alpha=0.5)\n",
"ax.axhline(y=80, color='gray', linestyle='--', alpha=0.5)\n",
"# Find the rank at 50% and 80%\n",
"rank_50 = holders_clean[holders_clean['cumulative_perc'] >= 0.5].iloc[0]['rank_number']\n",
"rank_80 = holders_clean[holders_clean['cumulative_perc'] >= 0.8].iloc[0]['rank_number']\n",
"ax.axvline(x=rank_50, color='gray', linestyle=':', alpha=0.5)\n",
"ax.axvline(x=rank_80, color='gray', linestyle=':', alpha=0.5)\n",
"ax.text(rank_50 + 5, 48, f'50% at rank {int(rank_50)}', fontsize=9)\n",
"ax.text(rank_80 + 5, 78, f'80% at rank {int(rank_80)}', fontsize=9)\n",
"ax.set_xlabel('Holder Rank')\n",
"ax.set_ylabel('Cumulative % of Supply')\n",
"ax.set_title('Token Concentration Curve')\n",
"ax.set_xlim(0, min(200, len(holders_clean)))\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/holder_concentration.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"top1 = holders_clean.iloc[0]\n",
"top10_pct = holders_clean.head(10)['cumulative_perc'].iloc[-1]\n",
"print(f'\\nHolder Concentration:')\n",
"print(f' Total holders: {len(holders_clean)}')\n",
"print(f' Top 1 ({top1[\"ens_name\"]}): {top1[\"perc\"]:.1%} of supply')\n",
"print(f' Top 10: {top10_pct:.1%} of supply')\n",
"print(f' 50% of supply held by top {int(rank_50)} holders')\n",
"print(f' 80% of supply held by top {int(rank_80)} holders')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 6. Holder Growth Over Time"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"holder_hist['date'] = pd.to_datetime(holder_hist['Date'])\n",
"\n",
"fig, axes = plt.subplots(2, 1, figsize=(16, 10))\n",
"\n",
"# Total holders over time\n",
"ax = axes[0]\n",
"for chain in holder_hist['chain'].unique():\n",
" subset = holder_hist[holder_hist['chain'] == chain].sort_values('date')\n",
" ax.plot(subset['date'], subset['Holders'], label=chain, linewidth=1.5)\n",
"ax.set_ylabel('Total Holders')\n",
"ax.set_title('TEC Token Holders Over Time')\n",
"ax.legend()\n",
"\n",
"# Net holder changes\n",
"ax = axes[1]\n",
"for chain in holder_hist['chain'].unique():\n",
" subset = holder_hist[holder_hist['chain'] == chain].sort_values('date')\n",
" colors = ['#2ecc71' if c >= 0 else '#e74c3c' for c in subset['Changes']]\n",
" ax.bar(subset['date'], subset['Changes'], color=colors, width=1, alpha=0.6, label=chain)\n",
"ax.axhline(y=0, color='black', linewidth=0.5)\n",
"ax.set_ylabel('Net Holder Change')\n",
"ax.set_title('Daily Net Holder Changes (+ = new holders, - = leaving)')\n",
"ax.legend()\n",
"\n",
"for ax in axes:\n",
" ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/holder_growth.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"net_change = holder_hist.groupby('chain')['Changes'].sum()\n",
"print(f'\\nHolder dynamics:')\n",
"for chain, change in net_change.items():\n",
" peak = holder_hist[holder_hist['chain'] == chain]['Holders'].max()\n",
" final = holder_hist[holder_hist['chain'] == chain].sort_values('date')['Holders'].iloc[-1]\n",
" print(f' {chain}: peak {peak} holders, final {final}, net change {change:+d}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 7. DEX Volume & Liquidity"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# DEX trade volume over time\n",
"dex_all['amount_usd_num'] = pd.to_numeric(dex_all['amount_usd'], errors='coerce')\n",
"dex_all['month'] = dex_all['date'].dt.to_period('M').dt.to_timestamp()\n",
"\n",
"dex_monthly = dex_all.groupby(['month', 'project']).agg(\n",
" trade_count=('block_time', 'count'),\n",
" volume_usd=('amount_usd_num', 'sum'),\n",
").reset_index()\n",
"\n",
"fig, axes = plt.subplots(2, 1, figsize=(16, 10))\n",
"\n",
"# Trade count\n",
"ax = axes[0]\n",
"for project in dex_monthly['project'].unique():\n",
" subset = dex_monthly[dex_monthly['project'] == project]\n",
" ax.bar(subset['month'], subset['trade_count'], width=20, alpha=0.7, label=project)\n",
"ax.set_ylabel('Trade Count')\n",
"ax.set_title('Monthly DEX Trade Count')\n",
"ax.legend()\n",
"\n",
"# USD Volume (where available)\n",
"ax = axes[1]\n",
"vol = dex_monthly[dex_monthly['volume_usd'] > 0]\n",
"if len(vol) > 0:\n",
" for project in vol['project'].unique():\n",
" subset = vol[vol['project'] == project]\n",
" ax.bar(subset['month'], subset['volume_usd'], width=20, alpha=0.7, label=project)\n",
" ax.set_ylabel('Volume (USD)')\n",
" ax.set_title('Monthly DEX Volume (USD)')\n",
" ax.legend()\n",
"else:\n",
" ax.text(0.5, 0.5, 'USD volume data not available for most trades',\n",
" transform=ax.transAxes, ha='center', fontsize=12)\n",
"\n",
"for ax in axes:\n",
" ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig(f'{DATA}/../snapshots/dex_volume.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"print(f'\\nDEX Activity:')\n",
"print(f' Total trades: {len(dex_all)}')\n",
"for project in dex_all['project'].unique():\n",
" n = len(dex_all[dex_all['project'] == project])\n",
" print(f' {project}: {n} trades')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 8. Key Findings"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print('=' * 70)\n",
"print('ABC & MARKET ANALYSIS — KEY FINDINGS')\n",
"print('=' * 70)\n",
"print(f'''\n",
"BONDING CURVE MECHANICS:\n",
" Price peak: {abc_sorted[\"price_per_token\"].max():.2f} xDAI\n",
" Price final: {abc_sorted[\"price_per_token\"].iloc[-1]:.2f} xDAI\n",
" Drawdown: {(abc_sorted[\"price_per_token\"].iloc[-1] / abc_sorted[\"price_per_token\"].max() - 1):.0%}\n",
" Reserve peak: {abc_sorted[\"reserve_balance\"].max():,.0f} xDAI → {abc_sorted[\"reserve_balance\"].iloc[-1]:,.0f}\n",
"\n",
"STRUCTURAL SELL PRESSURE:\n",
" Buy transactions: {len(buys)} ({len(buys)/(len(buys)+len(sells)):.0%})\n",
" Sell transactions: {len(sells)} ({len(sells)/(len(buys)+len(sells)):.0%})\n",
" Sell:Buy ratio: {len(sells)/len(buys):.1f}:1\n",
" The ABC experienced persistent, overwhelming sell pressure.\n",
" \n",
"TRIBUTE REVENUE:\n",
" Total: {tribute_by_action.sum():,.0f} xDAI\n",
" This was far too small to sustain common pool operations.\n",
"\n",
"HOLDER CONCENTRATION:\n",
" {len(holders_clean)} total holders\n",
" Top holder ({holders_clean.iloc[0][\"ens_name\"]}): {holders_clean.iloc[0][\"perc\"]:.1%}\n",
" Top 10: {top10_pct:.1%}\n",
"\n",
"CRITICAL QUESTIONS:\n",
" 1. Did the ABC entry tribute discourage buying? (effectively a tax on participation)\n",
" 2. Did the exit tribute slow selling enough, or just reduce returns?\n",
" 3. Was the reserve ratio appropriate for the level of volatility?\n",
" 4. Could the ABC have functioned better with different parameters?\n",
" 5. Did DEX liquidity create a cheaper exit path that undermined the ABC?\n",
"''')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}