Skip to content

Commit

Permalink
Explore ticks (#21)
Browse files Browse the repository at this point in the history
* exploratory uniswap agent to explore target grid price

* docstrings added

* docstrings added

* exploratory agent looking up all ticks

* clean docstrings and comments

* add kwargs to DummyAgent so that it accepts mu and sigma

* block number added as parameter and docstring typo fixed
  • Loading branch information
msabvid authored Mar 20, 2024
1 parent b495c40 commit 35a70b8
Show file tree
Hide file tree
Showing 6 changed files with 5,952 additions and 9,008 deletions.
10 changes: 7 additions & 3 deletions aave_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
type=str,
help="Generate a new request cache file.",
)
parser.add_argument(
"--block",
type=int,
default=19163600,
help="Ethereum Block number for mainnet forking",
)

args = parser.parse_args()

Expand All @@ -53,12 +59,10 @@
), "Alchemy key required, set with '--alchemy_key' argument"
cache = sim.init_cache(
args.alchemy_key,
19163600,
args.block,
args.seed,
args.n_steps,
args.n_borrow_agents,
args.mu,
args.sigma,
)
else:
cache = verbs.utils.cache_from_json(cache_json)
Expand Down
32 changes: 30 additions & 2 deletions uniswap_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,45 @@
"--n_steps", type=int, default=100, help="Number of steps of the simulation"
)
parser.add_argument("--sigma", type=float, default=0.3, help="GBM volatility")
parser.add_argument("--mu", type=float, default=0.0, help="GBM drift")
parser.add_argument(
"--batch_runner",
action="store_true",
help="Run batch of simulations over different simulation parameters",
)
parser.add_argument(
"--cache",
action="store_true",
help="Generate a new request cache file.",
)
parser.add_argument(
"--alchemy_key",
type=str,
help="Generate a new request cache file.",
)
parser.add_argument(
"--block",
type=int,
default=19163600,
help="Ethereum Block number for mainnet forking",
)
args = parser.parse_args()

with open(os.path.join("verbs_examples", "uniswap", "cache.json"), "r") as f:
cache_json = json.load(f)

cache = verbs.utils.cache_from_json(cache_json)
if args.cache:
assert (
args.alchemy_key is not None
), "Alchemy key required, set with '--alchemy_key' argument"
cache = sim.init_cache(
args.alchemy_key,
args.block,
args.seed,
args.n_steps,
)
else:
cache = verbs.utils.cache_from_json(cache_json)

if args.batch_runner:
# run a batch of simulations
Expand All @@ -54,7 +82,7 @@
env,
args.seed,
args.n_steps,
mu=0.0,
mu=args.mu,
sigma=args.sigma,
show_progress=True,
)
Expand Down
7,546 changes: 2,993 additions & 4,553 deletions verbs_examples/aave/cache.json

Large diffs are not rendered by default.

102 changes: 71 additions & 31 deletions verbs_examples/agents/uniswap_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def tick_from_price(sqrt_price_x96: int, uniswap_fee: int) -> int:
Lower tick of input price
"""
price = (sqrt_price_x96 / 2**96) ** 2
tick = math.floor(math.log(price, 1.001))
tick = math.floor(math.log(price, 1.0001))
tick_lower = tick - (tick % TICK_SPACING[uniswap_fee])
return tick_lower

Expand All @@ -48,7 +48,7 @@ def price_from_tick(tick: int) -> int:
int
Square root of price times 2\ :sup:`96`
"""
sqrt_price_x96 = np.sqrt(1.001**tick) * 2**96
sqrt_price_x96 = np.sqrt(1.0001**tick) * 2**96
return sqrt_price_x96


Expand Down Expand Up @@ -336,7 +336,7 @@ def _quote_price(change_token_1):
try:
sol = root_scalar(
lambda x: _quote_price(x) - sqrt_target_price_x96,
x0=change_token_1,
x0=change_token_1 // 2,
method="newton",
maxiter=5,
)
Expand Down Expand Up @@ -438,7 +438,7 @@ def _quote_price(change_token_1):
sol = root_scalar(
lambda x: _quote_price(x) - sqrt_target_price_x96,
method="newton",
x0=change_token_1,
x0=change_token_1 // 2,
maxiter=5,
)
change_token_1 = sol.root
Expand Down Expand Up @@ -577,7 +577,6 @@ def update(self, rng: np.random.Generator, env) -> List[verbs.types.Transaction]
list of transactions according to their policy.
The Uniswap agent will
* Check the price in the external market and in the Uniswap pool.
* Calculate the trade to do in Uniswap in order to realize a profit.
Expand Down Expand Up @@ -720,7 +719,7 @@ def get_price_impact_in_external_market(self, env) -> float:
return token_a_price_uniswap - token_a_price_external


class DummyUniswapAgent(UniswapAgent):
class DummyUniswapAgent(BaseUniswapAgent):
"""
Dummy uniswap agent used for cache generation
Expand All @@ -744,10 +743,8 @@ def __init__(
token_a_address: bytes,
# token B is considered to be less risky / stablecoin
token_b_address: bytes,
mu: float,
sigma: float,
dt: float,
sim_n_steps: int,
**kwargs
):
"""
Initialise the Uniswap agent and create the corresponding
Expand Down Expand Up @@ -807,32 +804,26 @@ def __init__(
swap_router_abi=swap_router_abi,
uniswap_pool_abi=uniswap_pool_abi,
quoter_abi=quoter_abi,
fee=fee,
swap_router_address=swap_router_address,
uniswap_pool_address=uniswap_pool_address,
quoter_address=quoter_address,
fee=fee,
token_a_address=token_a_address,
token_b_address=token_b_address,
mu=mu,
sigma=0,
dt=dt,
)
self.sim_n_steps = sim_n_steps

# calibrate mu to explore the pool
upper_bound_price = 1.7 * self.init_token_a_price
lower_bound_price = 0.3 * self.init_token_a_price

self.mu0 = (
1
/ (dt * float(sim_n_steps // 3))
* np.log(upper_bound_price / self.init_token_a_price)
)
self.mu1 = (
1
/ (dt * sim_n_steps - dt * float(sim_n_steps // 3))
* np.log(lower_bound_price / upper_bound_price)
slot0 = self.uniswap_pool_abi.slot0.call(
env, self.address, self.uniswap_pool_address, []
)[0]
init_tick = slot0[1]
self.ticks_to_explore = [
init_tick + (i + 1) * TICK_SPACING[fee] for i in range(sim_n_steps // 2)
]
self.ticks_to_explore.extend(
[init_tick - (i + 1) * TICK_SPACING[fee] for i in range(sim_n_steps // 2)]
)
self.step = 0

def update(self, rng: np.random.Generator, env) -> List[verbs.types.Transaction]:
"""
Expand All @@ -856,9 +847,58 @@ def update(self, rng: np.random.Generator, env) -> List[verbs.types.Transaction]
of the simulation. This can be an empty list if the
agent is not submitting any transactions.
"""
if self.step < self.sim_n_steps // 3:
self.external_market.mu = self.mu0

sqrt_target_price_x96 = price_from_tick(self.ticks_to_explore[self.step])
sqrt_price_uniswap_x96 = self.get_sqrt_price_x96_uniswap(env)

# get liquidity from uniswap pool
liquidity = self.uniswap_pool_abi.liquidity.call(
env, self.address, self.uniswap_pool_address, []
)[0][0]

if sqrt_target_price_x96 > sqrt_price_uniswap_x96:
swap_call = self.get_swap_size_to_increase_uniswap_price(
env=env,
sqrt_target_price_x96=sqrt_target_price_x96,
sqrt_price_uniswap_x96=sqrt_price_uniswap_x96,
liquidity=liquidity,
)
else:
swap_call = self.get_swap_size_to_decrease_uniswap_price(
env=env,
sqrt_target_price_x96=sqrt_target_price_x96,
sqrt_price_uniswap_x96=sqrt_price_uniswap_x96,
liquidity=liquidity,
)
self.step += 1

if swap_call is not None:
return [swap_call]
else:
self.external_market.mu = self.mu1
tx = super().update(rng, env)
return tx
return []

def record(self, env) -> Tuple[float, float]:
"""
Record the state of the agent
This method is called at the end of each step for all agents.
It should return any data to be recorded over the course
of the simulation.
Parameters
----------
env: verbs.types.Env
Network/EVM that the simulation interacts with.
Returns
-------
tuple[float, float]
Tuple containing:
- Price in Uniswap of token0 in terms of token1
- Price in the external market of token0 in terms of token1
"""
# Get sqrt price from uniswap pool. Uniswap returns price of
# token0 in terms of token1
sqrt_target_price_x96 = price_from_tick(self.ticks_to_explore[self.step - 1])
sqrt_price_uniswap_x96 = self.get_sqrt_price_x96_uniswap(env)
return sqrt_price_uniswap_x96**2, sqrt_target_price_x96**2
7,262 changes: 2,851 additions & 4,411 deletions verbs_examples/uniswap/cache.json

Large diffs are not rendered by default.

8 changes: 0 additions & 8 deletions verbs_examples/uniswap/sim.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,6 @@ def init_cache(
block_number: int,
seed: int,
n_steps: int,
mu: float = 0.1,
sigma: float = 0.6,
) -> verbs.types.Cache:
"""
Generate a simulation request cache
Expand All @@ -176,10 +174,6 @@ def init_cache(
Random seed
n_steps: int
Number of simulation steps
mu: float, optional
GBM mu parameter, default 0.1
sigma: float, optional
GBM sigma parameter, default 0.6
Returns
-------
Expand All @@ -197,8 +191,6 @@ def init_cache(
env,
seed,
n_steps,
mu=mu,
sigma=sigma,
uniswap_agent_type=partial(DummyUniswapAgent, sim_n_steps=n_steps),
)

Expand Down

0 comments on commit 35a70b8

Please sign in to comment.