Skip to content

Commit

Permalink
improve lp pnl calculation (#228)
Browse files Browse the repository at this point in the history
* include share reserves in LP PnL value
* add test_max_short
* add pytest.skip to dataclasses
* clear matplotlib warnings
* clarify get_max_short tests
  • Loading branch information
Mihai authored Feb 19, 2023
1 parent 8a348e5 commit 435f723
Show file tree
Hide file tree
Showing 7 changed files with 786 additions and 28 deletions.
22 changes: 8 additions & 14 deletions examples/notebooks/hyperdrive.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -394,9 +394,7 @@
"ylim = ax.get_ylim()\n",
"ax.set_ylim(0, ylim[1])\n",
"ax.set_yticks([i for i in np.arange(ylim[0], ylim[1], 0.01)])\n",
"ax.set_yticklabels([f\"{(i):.0%}\" for i in ax.get_yticks()])\n",
"\n",
"plt.show() "
"ax.set_yticklabels([f\"{(i):.0%}\" for i in ax.get_yticks()]);"
]
},
{
Expand Down Expand Up @@ -424,9 +422,7 @@
"\n",
"xtick_step = 10\n",
"ax.set_xticks([0]+[x for x in range(9, simulator.config.num_trading_days + 1, xtick_step)])\n",
"ax.set_xticklabels(['1']+[str(x+1) for x in range(9, simulator.config.num_trading_days + 1, xtick_step)])\n",
"\n",
"plt.show()"
"ax.set_xticklabels(['1']+[str(x+1) for x in range(9, simulator.config.num_trading_days + 1, xtick_step)]);"
]
},
{
Expand Down Expand Up @@ -476,9 +472,8 @@
"ax.set_xticklabels(['1']+[str(x+1) for x in range(9, simulator.config.num_trading_days + 1, xtick_step)])\n",
"\n",
"plt.gca().set_xlabel(\"Day\")\n",
"plt.gca().set_title('Trader PNL over time')\n",
"# display(data_no_mock)\n",
"plt.show()"
"plt.gca().set_title('Trader PNL over time');\n",
"# display(data_no_mock)"
]
},
{
Expand Down Expand Up @@ -524,8 +519,7 @@
"ax[1].set_xticks([0]+[x for x in range(9, simulator.config.num_trading_days + 1, xtick_step)])\n",
"ax[1].set_xticklabels(['1']+[str(x+1) for x in range(9, simulator.config.num_trading_days + 1, xtick_step)])\n",
"ylim = ax[1].get_ylim()\n",
"ax[1].set_ylim(0, ylim[1])\n",
"plt.show()"
"ax[1].set_ylim(0, ylim[1]);"
]
},
{
Expand Down Expand Up @@ -556,7 +550,7 @@
"provenance": []
},
"kernelspec": {
"display_name": "elf-env",
"display_name": "elf-env-3.11",
"language": "python",
"name": "python3"
},
Expand All @@ -570,11 +564,11 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.16"
"version": "3.11.1"
},
"vscode": {
"interpreter": {
"hash": "de650408e15adc8fb9b84ade33ab785e8e4285d1ab866a37f256293a74a6afc8"
"hash": "c5d2c6119c56bb6ee8800af392fefd9822dd75dbd861150440c37a69b7854c11"
}
}
},
Expand Down
673 changes: 673 additions & 0 deletions examples/notebooks/vault_tracker.ipynb

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions src/elfpy/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,15 @@ def get_state(self, market: Market) -> dict:
.. todo:: TODO: return a dataclass instead of dict to avoid having to check keys & the get_state_keys func
"""
lp_token_value = (
market.market_state.share_reserves
* market.market_state.share_price
* (self.lp_tokens / market.market_state.lp_reserves)
if self.lp_tokens > 0
else 0.0
)
lp_token_value = 0
if self.lp_tokens > 0: # proceed further only if the agent has LP tokens
if market.market_state.lp_reserves > 0: # avoid divide by zero
share_of_pool = self.lp_tokens / market.market_state.lp_reserves
pool_value = (
market.market_state.bond_reserves * market.spot_price # in base
+ market.market_state.share_reserves * market.market_state.share_price # in base
)
lp_token_value = pool_value * share_of_pool # in base
share_reserves = market.market_state.share_reserves
# compute long values in units of base
longs_value = 0
Expand Down
4 changes: 4 additions & 0 deletions tests/pricing_models/calc_test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
from dataclasses import dataclass
from typing import Type, Optional

from pytest import skip

from elfpy.types import MarketState, Quantity, StretchedTime

skip(msg="These are dataclasses used for tests, not tests themselves", allow_module_level=True)


@dataclass
class CalcInGivenOutSuccessTestCase:
Expand Down
13 changes: 6 additions & 7 deletions tests/pricing_models/test_get_max.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,14 @@ class TestCaseGetMax:


class TestGetMax(unittest.TestCase):
"""Tests for the various get_max functions within the pricing model."""
"""Tests get_max_short and get_max_long functions within the pricing model."""

def test_get_max(self):
"""Tests that get_max_long and get_max_short are safe.
These tests ensure that trades made with get_max_long and get_max_short
will not put the market into a pathological state (i.e. the trade amount
is non-negative, the bond reserves is non-negative, the buffer
invariants hold, and the APR is still non-negative).
"""
Tests that get_max_long and get_max_short are safe, by checking
apr >= 0
share_price * market_state.share_reserves >= base_buffer
bond_reserves >= bond_buffer
"""
pricing_models: list[PricingModel] = [HyperdrivePricingModel(), YieldSpacePricingModel()]

Expand Down
84 changes: 84 additions & 0 deletions tests/test_max_short.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
tests that attempting to 500 and 5000 PTs against liquidity of $200 is scaled down properly, with and without init_lp
"""
from __future__ import annotations # types are strings by default in 3.11

import unittest
import logging

import utils_for_tests as test_utils # utilities for testing

from elfpy.types import Config
import elfpy.utils.outputs as output_utils # utilities for file outputs


class BaseParameterTest(unittest.TestCase):
"""Generic Parameter Test class"""

def run_base_trade_test(
self,
agent_policies,
override=None,
delete_logs=True,
):
"""Assigns member variables that are useful for many tests"""
output_utils.setup_logging(log_filename=".logging/test_max_short.log", log_level=logging.DEBUG)
config = Config()
config.num_trading_days = 3 # sim 3 days to keep it fast for testing
config.num_blocks_per_day = 3 # 3 block a day, keep it fast for testing
config.num_position_days = 90
config.init_lp = False
if override is not None:
for key, value in override.items():
setattr(config, key, value)
simulator = test_utils.setup_simulation_entities(config=config, agent_policies=agent_policies)
print(f"{simulator.agents=}")
print(f"running simulator with {len(simulator.agents)} agents")
simulator.run_simulation()
output_utils.close_logging(delete_logs=delete_logs)
return simulator


class GetMaxShortTests(BaseParameterTest):
"""Tests of the get_max_short function of the agent class"""

# this exact scenario causes a precision error with share_reserves = -9.313225746154785e-10
def test_max_short_500_with_init_shuffle_users(self):
"""set up a short that will attempt to trade more than possible, WITH init_lp"""
agent_policies = ["single_lp:amount_to_lp=200", "single_short:amount_to_trade=500"]
self.run_base_trade_test(agent_policies=agent_policies, override={"init_lp": True})

def test_max_short_500_with_init_deterministic(self):
"""set up a short that will attempt to trade more than possible, WITH init_lp"""
agent_policies = ["single_lp:amount_to_lp=200", "single_short:amount_to_trade=500"]
self.run_base_trade_test(agent_policies=agent_policies, override={"init_lp": True, "shuffle_users": False})

def test_max_short_5000_with_init_shuffle_users(self):
"""set up a short that will attempt to trade more than possible, WITH init_lp"""
agent_policies = ["single_lp:amount_to_lp=200", "single_short:amount_to_trade=5000"]
self.run_base_trade_test(agent_policies=agent_policies, override={"init_lp": True})

def test_max_short_5000_with_init_deterministic(self):
"""set up a short that will attempt to trade more than possible, WITH init_lp"""
agent_policies = ["single_lp:amount_to_lp=200", "single_short:amount_to_trade=5000"]
self.run_base_trade_test(agent_policies=agent_policies, override={"init_lp": True, "shuffle_users": False})

def test_max_short_500_with_init_shuffle_users_without_init_lp(self):
"""set up a short that will attempt to trade more than possible, WITHOUT init_lp"""
agent_policies = ["single_lp:amount_to_lp=200", "single_short:amount_to_trade=500"]
self.run_base_trade_test(agent_policies=agent_policies, override={"init_lp": False})

def test_max_short_500_with_init_deterministic_without_init_lp(self):
"""set up a short that will attempt to trade more than possible, WITHOUT init_lp"""
agent_policies = ["single_lp:amount_to_lp=200", "single_short:amount_to_trade=500"]
self.run_base_trade_test(agent_policies=agent_policies, override={"init_lp": False, "shuffle_users": False})

def test_max_short_5000_with_init_shuffle_users_without_init_lp(self):
"""set up a short that will attempt to trade more than possible, WITHOUT init_lp"""
agent_policies = ["single_lp:amount_to_lp=200", "single_short:amount_to_trade=5000"]
self.run_base_trade_test(agent_policies=agent_policies, override={"init_lp": False})

def test_max_short_5000_with_init_deterministic_without_init_lp(self):
"""set up a short that will attempt to trade more than possible, WITHOUT init_lp"""
agent_policies = ["single_lp:amount_to_lp=200", "single_short:amount_to_trade=5000"]
self.run_base_trade_test(agent_policies=agent_policies, override={"init_lp": False, "shuffle_users": False})
2 changes: 2 additions & 0 deletions tests/test_notebooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def test_notebook_execution(self):
for line in cell_source_lines.split("\n"): # parse line-by-line for checks
if line.startswith("%"): # skip jupyter magic commands
continue
if line.startswith("display("): # skip display commands, as they're notebook-specific
continue
code_lines.append(line)
cell_source = isp.transform_cell("\n".join(code_lines)) # recombine lines
file_source += cell_source + "\n"
Expand Down

1 comment on commit 435f723

@vercel
Copy link

@vercel vercel bot commented on 435f723 Feb 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.