diff --git a/NB1-BalancerAMM-V1.0.ipynb b/NB1-PoolExploration-0x8b6-V1.0.ipynb similarity index 57% rename from NB1-BalancerAMM-V1.0.ipynb rename to NB1-PoolExploration-0x8b6-V1.0.ipynb index a625967..c5251f1 100644 --- a/NB1-BalancerAMM-V1.0.ipynb +++ b/NB1-PoolExploration-0x8b6-V1.0.ipynb @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -162,8 +162,8 @@ "source": [ "### B1.3 State Update Functions & Policies\n", "\n", - "Balancer Simulations replicate Balancer Pool Transactions in state update functions.\n", - "For a detailed description on how we implement transactions, please visit the [Balancer Simulations documentation/Balancer Pool Functions](https://token-engineering-balancer.gitbook.io/balancer-simulations/v/master/additional-code-and-instructions/balancer-the-python-edition/balancer-pool-functions)." + "Balancer Simulations replicate Balancer Pool Transactions in state update functions. \n", + "For a detailed description, please visit the [Balancer Simulations documentation](https://token-engineering-balancer.gitbook.io/balancer-simulations/additional-code-and-instructions/balancer-the-python-edition)." ] }, { @@ -177,7 +177,9 @@ "2. Compute subsequent **actions in discrete timesteps**, store datetime and update the pool state variables\n", "3. Update external **USD prices in discrete timesteps**, and store datetime \n", "\n", - "For more information please visit the [Balancer Simulations documentation/V1.0 Model Overview](https://token-engineering-balancer.gitbook.io/balancer-simulations/v/master/balancer-simulations/v10nboverview)." + "For more information please visit the [Balancer Simulations documentation/Model Overview](https://token-engineering-balancer.gitbook.io/balancer-simulations/v/master/balancer-simulations/v10nboverview). \n", + "\n", + "To inject historical on-chain transactions to the model, reference the **actions.json of your pool below**." ] }, { @@ -205,21 +207,9 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'result' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0msteps_number\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'steps_number'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'# Steps '\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msteps_number\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m sim_config = config_sim(\n\u001b[1;32m 4\u001b[0m {\n\u001b[1;32m 5\u001b[0m \u001b[0;34m'N'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;31m# number of monte carlo runs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mNameError\u001b[0m: name 'result' is not defined" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "steps_number = result['steps_number']\n", "print('# Steps ', steps_number)\n", @@ -262,8 +252,7 @@ "- `token_k_values` (token value in USD) \n", "based on \n", "- `token_k_balances`\n", - "- `token_k_price` \n", - "\n" + "- `token_k_price` (external price feed)" ] }, { @@ -287,26 +276,18 @@ "source": [ "# C. Simulation Outcome & Pool Exploration\n", "\n", - "Below we show a range of plots exploring pool states in the simulation:\n", + "The plots below offer some keys metrics for pool analysis.\n", "\n", "**a) Pool Power:** \n", - "- C1.1 TVL (Total Value Locked, over time) compared to \n", - "- C1.1 Pool Size Growth (Number of tokens in the pool, over time) \n", - "- C1.2 Token Balances (individual balances, over time)\n", + "- C1.1 TVL (Total Value Locked) vs. Pool Growth (token balances in the pool, over time)\n", + "- C1.2 Token Balances (individual token balances, over time)\n", "\n", "**b) Pool Characteristics:** \n", - "- C1.3 Source of Pool Growth (Total Growth vs. Fees collected)\n", + "- C1.3 Sources of Pool Growth (BPT, Fees)\n", "- C1.4 Token Ratio\n", "- C1.5 Action Types" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, @@ -326,8 +307,9 @@ "outputs": [], "source": [ "print(\"Observation Time\")\n", - "print(f\"Start: {(p_df.iloc[0]['change_datetime'])}\")\n", - "print(f\"End: {(p_df.iloc[-1]['change_datetime'])}\")\n", + "print(f\"Start: {p_df['change_datetime'].min()}\")\n", + "print(f\"End: {p_df['change_datetime'].max()}\")\n", + "print(f\"Total Observation Period: {p_df['change_datetime'].max() - p_df['change_datetime'].min()}\")\n", "print(\"\\n\")\n", "print(f\"Total No. of Timesteps (incl. Price Updates): {(p_df.iloc[-1]['timestep'])}\")\n", "print(f\"Total No. of Transactions: {len(p_df[p_df.action_type != 'external_price_update'])}\")" @@ -347,17 +329,13 @@ "outputs": [], "source": [ "#TVL vs. Pool Size Growth\n", - "tvl_p_df = p_df[['timestep', 'tvl', 'total_token_balances']] \n", - "# Create figure with secondary y-axis\n", - "fig = make_subplots(specs=[[{\"secondary_y\": True}]])\n", - "# Add traces\n", - "fig.add_trace(go.Scatter(x=tvl_p_df['timestep'],y=tvl_p_df['tvl'], line=dict(color='#7DFD64'), name=\"TVL total_token_value\"), secondary_y=False,)\n", - "fig.add_trace(go.Scatter(x=tvl_p_df['timestep'],y=tvl_p_df['total_token_balances'], line=dict(color='#2C1839'), name=\"total_token_balance\"), secondary_y=True,)\n", - "#Layout\n", - "fig.update_layout(title_text=\"TVS vs. Pool Size Growth\")\n", - "fig.update_xaxes(title_text=\"timestep\")\n", - "fig.update_yaxes(title_text=\"TVL total_token_value in USD\", secondary_y=False)\n", - "fig.update_yaxes(title_text=\"total_token_balance in #\", secondary_y=True)\n", + "fig = make_subplots(specs=[[{'secondary_y': True}]])\n", + "fig.add_trace(go.Scatter(x=p_df.timestep, y=p_df.tvl, line=dict(color='#f36315'), name=\"TVL total_token_value\"), secondary_y=False)\n", + "fig.add_trace(go.Scatter(x=p_df.timestep,y=p_df.total_token_balances, line=dict(color='#2C1839'), name=\"total_token_balance\"), secondary_y=True)\n", + "fig.update_layout(title_text='TVL vs. Pool Growth')\n", + "fig.update_xaxes(title_text='timestep')\n", + "fig.update_yaxes(title_text='TVL total_token_value in USD', secondary_y=False)\n", + "fig.update_yaxes(title_text='total_token_balance in #', secondary_y=True)\n", "fig.show() " ] }, @@ -366,20 +344,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "#TVL growth over observation time in %\n", - "#Total token balance growth over observation time in %" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Observations:**\n", - "- both TVL and Pool Size grow, but\n", - "- Pool Size growth is huge - \n", - "- USD value spikes around timestep 5000" - ] + "source": [] }, { "cell_type": "markdown", @@ -391,27 +356,27 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [], "source": [ - "k = 2 #define number of tokens in your pool\n", - "fig = make_subplots(rows=k, cols=1)\n", - "fig.add_trace(go.Scatter(x=p_df['timestep'], y=p_df['token_weth_balance'], line=dict(color='#015B99'), name='token_weth_balance'), row=1, col=1)\n", - "fig.add_trace(go.Scatter(x=p_df['timestep'], y=p_df['token_dai_balance'], line=dict(color='#5CB1EC'), name='token_dai_balance'), row=2, col=1)\n", - "fig.update_layout(height=400, width=1000, title_text=\"Token Balances in #\")\n", + "#balance growth % (total observation period)\n", + "dai_growth = ((p_df.iloc[-1][['token_dai_balance']])-(p_df.iloc[0][['token_dai_balance']]))*100/(p_df.iloc[0][['token_dai_balance']])\n", + "weth_growth = ((p_df.iloc[-1][['token_weth_balance']])-(p_df.iloc[0][['token_weth_balance']]))*100/(p_df.iloc[0][['token_weth_balance']])\n", + "growth = list(zip(dai_growth, weth_growth)) \n", + "g_df = pd.DataFrame(growth, columns = ['dai_growth','weth_growth' ]).transpose(copy=True).reset_index()\n", + "g_df.columns =['token', 'growth']\n", + "print(g_df)\n", + "#plot\n", + "fig = make_subplots(rows=2, cols=2, subplot_titles=('DAI Growth', 'Balance Growth in %', 'WETH Growth'))\n", + "fig.add_trace(go.Scatter(x=p_df.timestep, y=p_df.token_dai_balance, line=dict(color='#4675ed'), name='token_dai_balance'), row=1, col=1)\n", + "fig.add_trace(go.Scatter(x=p_df.timestep, y=p_df.token_weth_balance, line=dict(color='#4145ab'), name='token_weth_balance'), row=2, col=1)\n", + "fig.add_trace(go.Bar(y=g_df.growth, x=g_df.token, marker_color=['#4675ed','#4145ab']), row=1, col=2)\n", + "fig.update_layout(height=600, width=1000, showlegend=False, title_text='Balance Growth in observation period')\n", "fig.show()" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Observations:**\n", - "- ETH declines, DAI grows significantly\n", - "- Pool Size growth mainly driven by DAI growth\n", - "- Hypothesis: pool's main purpose is to serve as an exchange ETH for DAI (see also Action Types below)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -426,23 +391,13 @@ "outputs": [], "source": [ "#Pool Shares\n", - "ps_p_df = p_df[['timestep','pool_shares']]\n", - "fig = px.line(ps_p_df, x=ps_p_df['timestep'],y=ps_p_df['pool_shares'])\n", - "fig.update_layout(height=300, width=1000, title_text=\"Pool Shares (BPT)\")\n", - "fig.update_xaxes(title_text=\"timestep\")\n", - "fig.update_yaxes(title_text=\"pool shares in #\", range=[99.985,101.00])\n", + "fig = px.line(ps, x=p_df.timestep,y=p_df.pool_shares)\n", + "fig.update_layout(height=300, width=1000, title_text='Pool Shares (BPT)')\n", + "fig.update_xaxes(title_text='timestep')\n", + "fig.update_yaxes(title_text='pool shares in #', range=[99.985,101.00])\n", "fig.show()" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Observations:**\n", - "- minor growth via liquidity deposits\n", - "- Hypothesis: fees are main source of Pool Growth" - ] - }, { "cell_type": "code", "execution_count": null, @@ -450,12 +405,11 @@ "outputs": [], "source": [ "#Fees\n", - "k = 2 #define number of tokens in your pool\n", - "fig = make_subplots(rows=k, cols=1)\n", - "fee_p_df = p_df[['timestep','generated_fees_dai','token_dai_price','generated_fees_weth','token_weth_price']]\n", - "fig.add_trace(go.Scatter(x=p_df['timestep'], y=p_df['generated_fees_weth'], line=dict(color='#015B99'), name='fees generated WETH'), row=1, col=1)\n", - "fig.add_trace(go.Scatter(x=p_df['timestep'], y=p_df['generated_fees_dai'], line=dict(color='#5CB1EC'), name='fees generated DAI'), row=2, col=1)\n", - "fig.update_layout(height=400, width=1000, title_text=\"Fees Generated over time\")" + "fig = make_subplots(rows=2, cols=1)\n", + "fig.add_trace(go.Scatter(x=p_df.timestep, y=p_df.generated_fees_dai, line=dict(color='#4675ed'), name='fees DAI'), row=1, col=1)\n", + "fig.add_trace(go.Scatter(x=p_df.timestep, y=p_df.generated_fees_weth, line=dict(color='#4145ab'), name='fees WETH'), row=2, col=1)\n", + "fig.update_yaxes(title_text='token amount', secondary_y=False)\n", + "fig.update_layout(height=500, width=1000, title_text='Fees Generated per transaction')" ] }, { @@ -464,7 +418,15 @@ "metadata": {}, "outputs": [], "source": [ - "p_df.info()" + "# DAI fee events vs. ETH fee events\n", + "WETH_fee_events = len(p_df[p_df.generated_fees_weth > 0])\n", + "DAI_fee_events = len(p_df[p_df.generated_fees_dai > 0])\n", + "values = [DAI_fee_events, WETH_fee_events]\n", + "labels = ['DAI fees', 'WETH fees']\n", + "fig = go.Figure(data=[go.Pie(labels=labels, values=values)])\n", + "fig.update_traces(hoverinfo='label+percent', marker=dict(colors=['#4675ed','#4145ab']))\n", + "fig.update_layout(height=500, width=1000, title_text='Fee generating events in total observation time')\n", + "fig.show()" ] }, { @@ -473,33 +435,25 @@ "metadata": {}, "outputs": [], "source": [ - "#balance growth (total observation period)\n", - "daib_df = (p_df.iloc[-1][['token_dai_balance']])-(p_df.iloc[0][['token_dai_balance']]).copy()\n", - "daib_df = daib_df.reset_index()\n", - "daib_df.columns =['source', 'growth']\n", - "wethb_df = (p_df.iloc[-1][['token_weth_balance']])-(p_df.iloc[0][['token_weth_balance']]).copy()\n", - "wethb_df = wethb_df.reset_index()\n", - "wethb_df.columns =['source', 'growth']\n", - "\n", - "#fee growth ACHTUNG add \"generated_fees_k\", is currently an object, this is why I use price atm\n", - "daif_df = p_df.groupby('action_type').sum()['token_dai_price'].drop(['exit', 'exit_swap', 'external_price_update','pool_creation']).reset_index()\n", - "daif_df.columns =['source', 'growth'] \n", - "wethf_df = p_df.groupby('action_type').sum()['token_weth_price'].drop(['exit', 'exit_swap', 'external_price_update','pool_creation']).reset_index()\n", - "wethf_df.columns =['source', 'growth'] \n", + "#proportions of fee value contribution, in fixed USD value\n", + "daif = p_df.groupby('action_type').sum()['generated_fees_dai'].drop(['exit', 'join', 'external_price_update','pool_creation'])\n", + "wethf = p_df.groupby('action_type').sum()['generated_fees_weth'].drop(['exit', 'join', 'external_price_update','pool_creation'])\n", + "fee_v = pd.concat([daif, wethf], axis=1)\n", "\n", - "#merge\n", - "dai_df= pd.concat([daib_df, daif_df], ignore_index=True)\n", - "dai_df\n", - "weth_df= pd.concat([wethb_df, wethf_df], ignore_index=True)\n", - "weth_df\n", + "#calculate USD value (define USD value, in this case locked to initial state observation time)\n", + "fee_v['DAI_fee_value'] = fee_v.generated_fees_dai*1.0053414509361551 #define DAI price\n", + "fee_v['WETH_fee_value'] = fee_v.generated_fees_weth*594.3526451552318 #define WETH price\n", "\n", - "#plot chart\n", - "k = 2 #define number of tokens in your pool\n", - "fig = make_subplots(rows=1, cols=k, subplot_titles=(\"DAI Growth\", \"WETH Growth\"))\n", - "fig.add_trace(go.Bar(name=\"DAI Growth\", y=dai_df['growth'], x=dai_df['source'], marker_color=px.colors.sequential.Turbo), row=1, col=1)\n", - "fig.add_trace(go.Bar(name=\"WETH Growth\", y=weth_df['growth'], x=weth_df['source'], marker_color=px.colors.sequential.Turbo), row=1, col=2)\n", - "fig.update_layout(height=400, width=1000, title_text=\"Growth & Sources of Growth\", showlegend=False)\n", + "#add total \n", + "fee_v = fee_v.append(fee_v.sum().rename('total'))\n", + "print(fee_v)\n", "\n", + "#plot pie chart comparing ETH fees vs DAI fees\n", + "values = [fee_v.loc['total']['DAI_fee_value'], fee_v.loc['total']['WETH_fee_value']] \n", + "labels = ['DAI_fees', 'WETH_fees']\n", + "fig = go.Figure(data=[go.Pie(labels=labels, values=values)])\n", + "fig.update_traces(hoverinfo='label+percent', marker=dict(colors=['#4675ed','#4145ab']))\n", + "fig.update_layout(height=500, width=1000, title_text=\"Fee value contribution in USD, in total observation time\")\n", "fig.show()" ] }, @@ -509,8 +463,17 @@ "metadata": {}, "outputs": [], "source": [ - "# Compare USD value of fees collected ETH/DAI\n", - "# Compare % of USD value total fees collected via DAI vs. ETH" + "# FEE PER DAY/in fixed USD value\n", + "\n", + "#calculate USD value (define USD value, in this case locked to initial state observation time)\n", + "fee_t = p_df[['change_datetime']].dropna()\n", + "fee_t['DAI_fee_value'] = p_df.generated_fees_dai*1.0053414509361551 #define DAI price\n", + "fee_t['WETH_fee_value'] = p_df.generated_fees_weth*594.3526451552318 #define WETH price\n", + "daily = fee_t.groupby(pd.Grouper(key='change_datetime',freq='D')).sum() #sum per week\n", + "\n", + "#plot value per week\n", + "fig = go.Figure(data=[go.Bar(name='DAI fees', x=daily.index, y=daily.DAI_fee_value, marker_color='#4675ed', offsetgroup=0), go.Bar(name='WETH fees', x=daily.index, y=daily.WETH_fee_value, marker_color='#4145ab', offsetgroup=1)], layout=go.Layout(title='Fees per day in USD', yaxis_title='USD value'))\n", + "fig.show()" ] }, { @@ -526,22 +489,25 @@ "metadata": {}, "outputs": [], "source": [ - "# TODO: create subplots\n", - "# TODO: alternatively consider line chart showing changes across particular events\n", - "\n", - "events = [0, -1]\n", - "r_p_df = p_df.iloc[events][['token_dai_balance','token_weth_balance']].transpose(copy=True).reset_index()\n", - "r_p_df\n", - "\n", - "fig1 = px.pie(r_p_df, values=0, names='index', color_discrete_sequence=px.colors.sequential.Turbo)\n", - "fig2 = px.pie(r_p_df, values=25601, names='index', color_discrete_sequence=px.colors.sequential.Turbo)\n", - "fig1.update_layout(height=300, width=1000, title_text=\"Token Ratio\")\n", - "fig2.update_layout(height=300, width=1000, title_text=\"Token Ratio\")\n", - "\n", - "fig1.show()\n", - "fig2.show()" + "events = [0, -1] #Start/End of observation period\n", + "ratio = pd.DataFrame(p_df.iloc[events][['token_dai_balance','token_weth_balance']]).transpose(copy=True)\n", + "ratio.columns =['start', 'end'] \n", + "#plot\n", + "fig = make_subplots(rows=1, cols=2, specs=[[{'type':'domain'}, {'type':'domain'}]])\n", + "fig.add_trace(go.Pie(labels=ratio.index, values=ratio.start), 1, 1)\n", + "fig.add_trace(go.Pie(labels=ratio.index, values=ratio.end), 1, 2)\n", + "fig.update_traces(hoverinfo='label+percent+name', marker=dict(colors=['#4675ed','#4145ab']))\n", + "fig.update_layout(height=400, width=1000, title_text='Token Ratio Start/End Observation Period in %')\n", + "fig.show()" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "metadata": {}, @@ -555,8 +521,37 @@ "metadata": {}, "outputs": [], "source": [ - "#action type per timestep\n", - "fig = px.scatter(p_df, x='timestep', y='total_token_balances', color='action_type', color_discrete_sequence=px.colors.sequential.Turbo, category_orders={'action_type': ['pool_creation', 'join', 'join_swap', 'swap', 'exit_swap', 'exit', 'external_price_update']})\n", + "# Action Types by proportion\n", + "actions = p_df.groupby('action_type').size().to_frame('count').reset_index()\n", + "fig = px.pie(actions, values='count', names='action_type', title='Action Types', hole=.3, color_discrete_sequence=px.colors.sequential.Turbo)\n", + "fig.show()\n", + "print(actions)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "#Action Type per timestep\n", + "fig = px.scatter(p_df, x='timestep', y='total_token_balances', color='action_type', color_discrete_sequence=px.colors.sequential.Turbo, category_orders={'action_type': ['swap', 'external_price_update', 'join_swap', 'join', 'exit_swap', 'exit', 'pool_creation']})\n", "fig.update_layout(height=400, width=1000, title_text=\"Action Type / Timestep\")\n", "fig.update_xaxes(rangeslider_visible=True)\n", "fig.show()" @@ -567,22 +562,21 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "# Show Action Types\n", - "a_p_df = p_df.groupby('action_type').size().to_frame('count').reset_index()\n", - "fig = px.pie(a_p_df, values='count', names='action_type', title='Action Types', hole=.3, color_discrete_sequence=px.colors.sequential.Turbo)\n", - "fig.show()\n", - "print(a_p_df)" - ] + "source": [] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, - "source": [ - "**Observations:**\n", - "\n", - "- Hypothesis confirmed, pool's main purpose is to serve as an exchange ETH for DAI (see Pool Size Growth)\n" - ] + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": {