Skip to content

Commit

Permalink
replace elfpy market with ethpy HyperdriveInterface (#909)
Browse files Browse the repository at this point in the history
This PR modifies the agent0 policies & accounts to use the new
HyperdriveInterface API instead of the elfpy HyperdriveMarket.

The new interface lacks some features (e.g. all of the yieldspace math
and variable rates) that were required for the smart long and short
policies, so we are deleting those for now. I made several new issues to
detail follow-up work that is still needed. They are #910 #911 #913

This PR also gets us very close to completely detaching elfpy from the
rest of the monorepo, though the rest of the detachment should also
happen in a follow-up PR.
  • Loading branch information
dpaiton authored Sep 28, 2023
1 parent 2980e73 commit b23b140
Show file tree
Hide file tree
Showing 23 changed files with 156 additions and 582 deletions.
12 changes: 6 additions & 6 deletions lib/agent0/agent0/base/agents/eth_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
from web3 import Web3

Policy = TypeVar("Policy", bound=BasePolicy)
Market = TypeVar("Market")
MarketInterface = TypeVar("MarketInterface")
MarketAction = TypeVar("MarketAction")


class EthAgent(LocalAccount, Generic[Policy, Market, MarketAction]):
class EthAgent(LocalAccount, Generic[Policy, MarketInterface, MarketAction]):
r"""Enact policies on smart contracts and tracks wallet state"""

def __init__(self, account: LocalAccount, policy: Policy | None = None):
Expand Down Expand Up @@ -75,17 +75,17 @@ def checksum_address(self) -> ChecksumAddress:
"""Return the checksum address of the account"""
return Web3.to_checksum_address(self.address)

def get_trades(self, market: Market) -> list[Trade[MarketAction]]:
def get_trades(self, interface: MarketInterface) -> list[Trade[MarketAction]]:
"""Helper function for computing a agent trade
Arguments
----------
market : Market
The market on which this agent will be executing trades (MarketActions)
interface : MarketInterface
Interface for the market on which this agent will be executing trades (MarketActions)
Returns
-------
list[Trade]
List of Trade type objects that represent the trades to be made by this agent
"""
return self.policy.action(market, self.wallet)
return self.policy.action(interface, self.wallet)
10 changes: 4 additions & 6 deletions lib/agent0/agent0/base/policies/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,15 @@

if TYPE_CHECKING:
from agent0.base.state import EthWallet

# from agent0.base.state import BaseMarketState # TODO: don't rely on elfpy base market
from elfpy.markets.base import BaseMarket as BaseMarketState
from elfpy.types import Trade
from ethpy.base import BaseInterface
from numpy.random._generator import Generator as NumpyGenerator

Wallet = TypeVar("Wallet", bound="EthWallet")
MarketState = TypeVar("MarketState", bound="BaseMarketState")
MarketInterface = TypeVar("MarketInterface", bound="BaseInterface")


class BasePolicy(Generic[MarketState, Wallet]):
class BasePolicy(Generic[MarketInterface, Wallet]):
"""Base class policy."""

def __init__(
Expand All @@ -42,6 +40,6 @@ def name(self):
"""Return the class name"""
return self.__class__.__name__

def action(self, market: MarketState, wallet: Wallet) -> list[Trade]:
def action(self, interface: MarketInterface, wallet: Wallet) -> list[Trade]:
"""Returns an empty list, indicating no action"""
raise NotImplementedError
6 changes: 3 additions & 3 deletions lib/agent0/agent0/base/policies/no_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from fixedpointmath import FixedPoint

from .base import BasePolicy, MarketState, Wallet
from .base import BasePolicy, MarketInterface, Wallet

if TYPE_CHECKING:
from elfpy.types import Trade
Expand All @@ -15,7 +15,7 @@
# pylint: disable=too-few-public-methods


class NoActionPolicy(BasePolicy[MarketState, Wallet]):
class NoActionPolicy(BasePolicy[MarketInterface, Wallet]):
"""NoOp class policy"""

def __init__(self, budget: FixedPoint | None = None, rng: NumpyGenerator | None = None):
Expand All @@ -24,7 +24,7 @@ def __init__(self, budget: FixedPoint | None = None, rng: NumpyGenerator | None
else:
super().__init__(budget, rng)

def action(self, market: MarketState, wallet: Wallet) -> list[Trade]:
def action(self, interface: MarketInterface, wallet: Wallet) -> list[Trade]:
"""Returns an empty list, indicating no action"""
# pylint: disable=unused-argument
return []
1 change: 0 additions & 1 deletion lib/agent0/agent0/base/state/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
"""Base classes for interface objects"""
from .eth_wallet import EthWallet, EthWalletDeltas
from .market_state import BaseMarketState
34 changes: 0 additions & 34 deletions lib/agent0/agent0/base/state/market_state.py

This file was deleted.

30 changes: 14 additions & 16 deletions lib/agent0/agent0/hyperdrive/agents/hyperdrive_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,26 @@
from __future__ import annotations

import logging
from typing import Generic, TypeVar
from typing import TypeVar

from agent0.base import Quantity, TokenType
from agent0.base.agents import EthAgent
from agent0.base.policies import BasePolicy
from agent0.hyperdrive.state import HyperdriveActionType, HyperdriveMarketAction, HyperdriveWallet
from elfpy.markets.hyperdrive import HyperdriveMarket
from elfpy.types import MarketType, Trade
from eth_account.signers.local import LocalAccount
from ethpy.hyperdrive import HyperdriveInterface
from hexbytes import HexBytes

Policy = TypeVar("Policy", bound=BasePolicy)
Market = TypeVar(
"Market", bound=HyperdriveMarket
) # TODO: I don't know how to impose that this is a HyperdriveMarket at times, but BaseMarket in general
MarketAction = TypeVar(
"MarketAction", bound=HyperdriveMarketAction
) # TODO: should be able to infer this from the market


class HyperdriveAgent(EthAgent, Generic[Policy, Market, MarketAction]):
r"""Enact policies on smart contracts and tracks wallet state"""
class HyperdriveAgent(EthAgent[Policy, HyperdriveInterface, HyperdriveMarketAction]):
r"""Enact policies on smart contracts and tracks wallet state
.. todo::
should be able to get the HyperdriveMarketAction type from the HyperdriveInterface
"""

def __init__(self, account: LocalAccount, policy: Policy | None = None):
"""Initialize an agent and wallet account
Expand Down Expand Up @@ -72,7 +70,7 @@ def __init__(self, account: LocalAccount, policy: Policy | None = None):
)

@property
def liquidation_trades(self) -> list[Trade[MarketAction]]:
def liquidation_trades(self) -> list[Trade[HyperdriveMarketAction]]:
"""List of trades that liquidate all open positions
Returns
Expand Down Expand Up @@ -126,12 +124,12 @@ def liquidation_trades(self) -> list[Trade[MarketAction]]:
)
return action_list

def get_trades(self, market: Market) -> list[Trade[MarketAction]]:
def get_trades(self, interface: HyperdriveInterface) -> list[Trade[HyperdriveMarketAction]]:
"""Helper function for computing a agent trade
Arguments
----------
market : Market
interface : HyperdriveInterface
The market on which this agent will be executing trades (MarketActions)
Returns
Expand All @@ -141,13 +139,13 @@ def get_trades(self, market: Market) -> list[Trade[MarketAction]]:
"""
# get the action list from the policy
# TODO: Deprecate the old wallet in favor of this new one
actions: list[Trade[MarketAction]] = self.policy.action(market, self.wallet)
actions: list[Trade[HyperdriveMarketAction]] = self.policy.action(interface, self.wallet)
# edit each action in place
for action in actions:
if action.market_type == MarketType.HYPERDRIVE and action.market_action.maturity_time is None:
# TODO market latest_checkpoint_time and position_duration should be in ints
action.market_action.maturity_time = int(market.latest_checkpoint_time) + int(
market.position_duration.seconds
action.market_action.maturity_time = (
interface.seconds_since_latest_checkpoint + interface.pool_config["positionDuration"]
)
if action.market_action.trade_amount <= 0:
raise ValueError("Trade amount cannot be zero or negative.")
Expand Down
13 changes: 3 additions & 10 deletions lib/agent0/agent0/hyperdrive/exec/execute_agent_trades.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
from agent0.base import Quantity, TokenType
from agent0.hyperdrive.state import HyperdriveActionType, HyperdriveMarketAction, HyperdriveWalletDeltas, Long, Short
from elfpy import types
from elfpy.markets.hyperdrive import HyperdriveMarket
from ethpy.base import UnknownBlockError
from ethpy.hyperdrive import HyperdriveInterface, get_hyperdrive_market
from ethpy.hyperdrive import HyperdriveInterface
from fixedpointmath import FixedPoint

if TYPE_CHECKING:
Expand All @@ -30,7 +29,6 @@ def assert_never(arg: NoReturn) -> NoReturn:
async def async_execute_single_agent_trade(
agent: HyperdriveAgent,
hyperdrive: HyperdriveInterface,
hyperdrive_market: HyperdriveMarket,
) -> None:
"""Executes a single agent's trade. This function is async as
`match_contract_call_to_trade` waits for a transaction receipt.
Expand All @@ -41,10 +39,8 @@ async def async_execute_single_agent_trade(
The HyperdriveAgent that is conducting the trade
hyperdrive : HyperdriveInterface
The Hyperdrive API interface object
hyperdrive_market: HyperdriveMarket:
The hyperdrive market state
"""
trades: list[types.Trade[HyperdriveMarketAction]] = agent.get_trades(market=hyperdrive_market)
trades: list[types.Trade[HyperdriveMarketAction]] = agent.get_trades(interface=hyperdrive)
for trade_object in trades:
logging.info(
"AGENT %s to perform %s for %g",
Expand Down Expand Up @@ -72,12 +68,9 @@ async def async_execute_agent_trades(
agents : list[HyperdriveAgent]
A list of HyperdriveAgent that are conducting the trades
"""
# NOTE: This might _not_ be the latest market, due to async
# get latest market
hyperdrive_market = get_hyperdrive_market(hyperdrive.web3, hyperdrive.hyperdrive_contract)
# Make calls per agent to execute_single_agent_trade
# Await all trades to finish before continuing
await asyncio.gather(*[async_execute_single_agent_trade(agent, hyperdrive, hyperdrive_market) for agent in agents])
await asyncio.gather(*[async_execute_single_agent_trade(agent, hyperdrive) for agent in agents])


async def async_match_contract_call_to_trade(
Expand Down
4 changes: 0 additions & 4 deletions lib/agent0/agent0/hyperdrive/policies/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@
# Base policy to subclass from
from .hyperdrive_policy import HyperdrivePolicy
from .random_agent import RandomAgent
from .smart_long import LongLouie
from .smart_short import ShortSally


class Policies(NamedTuple):
"""All policies in elfpy."""

random_agent = RandomAgent
long_louie = LongLouie
short_sally = ShortSally
9 changes: 4 additions & 5 deletions lib/agent0/agent0/hyperdrive/policies/hyperdrive_policy.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
"""Base class for hyperdrive policies"""

# from agent0.hyperdrive import HyperdriveMarketState # TODO: use agent0 market state instead of elfpy market
from agent0.base.policies import BasePolicy
from agent0.hyperdrive.state import HyperdriveMarketAction, HyperdriveWallet
from elfpy.markets.hyperdrive import HyperdriveMarket as HyperdriveMarketState
from elfpy.types import Trade
from ethpy.hyperdrive import HyperdriveInterface


class HyperdrivePolicy(BasePolicy[HyperdriveMarketState, HyperdriveWallet]):
class HyperdrivePolicy(BasePolicy[HyperdriveInterface, HyperdriveWallet]):
"""Hyperdrive policy."""

def action(self, market: HyperdriveMarketState, wallet: HyperdriveWallet) -> list[Trade[HyperdriveMarketAction]]:
"""Returns an empty list, indicating no action"""
def action(self, interface: HyperdriveInterface, wallet: HyperdriveWallet) -> list[Trade[HyperdriveMarketAction]]:
"""Returns an empty list, indicating no action."""
raise NotImplementedError
Loading

1 comment on commit b23b140

@vercel
Copy link

@vercel vercel bot commented on b23b140 Sep 28, 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.