Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interactive fuzz trades use correct max amount #1245

Merged
merged 9 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions lib/agent0/agent0/interactive_fuzz/fuzz_hyperdrive_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
from agent0.interactive_fuzz.helpers import (
FuzzAssertionException,
close_random_trades,
generate_trade_list,
open_random_trades,
execute_random_trades,
setup_fuzz,
)

Expand Down Expand Up @@ -74,17 +73,14 @@ def fuzz_hyperdrive_balance(num_trades: int, chain_config: LocalChain.Config, lo
pool_state
)

# Generate a list of agents that execute random trades
trade_list = generate_trade_list(num_trades, rng, interactive_hyperdrive)

# Open some trades
trade_events = open_random_trades(trade_list, chain, rng, interactive_hyperdrive, advance_time=True)
trade_events = execute_random_trades(num_trades, chain, rng, interactive_hyperdrive, advance_time=True)

# Close the trades
close_random_trades(trade_events, rng)

assert len(trade_list) > 0
agent = trade_list[0][0]
assert len(trade_events) > 0
agent = trade_events[0][0]

# Check the reserve amounts; they should be unchanged now that all of the trades are closed
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from agent0.hyperdrive.crash_report import build_crash_trade_result, log_hyperdrive_crash_report
from agent0.hyperdrive.interactive import InteractiveHyperdrive, LocalChain
from agent0.hyperdrive.interactive.event_types import CloseLong, CloseShort, OpenLong, OpenShort
from agent0.interactive_fuzz.helpers import FuzzAssertionException, generate_trade_list, open_random_trades, setup_fuzz
from agent0.interactive_fuzz.helpers import FuzzAssertionException, execute_random_trades, setup_fuzz

# main script has a lot of stuff going on
# pylint: disable=too-many-locals
Expand Down Expand Up @@ -86,12 +86,9 @@ def fuzz_long_short_maturity_values(
logging.info("Advance time...")
chain.advance_time(time_to_next_checkpoint + 100, create_checkpoints=True)

# Generate a list of agents that execute random trades
trade_list = generate_trade_list(num_trades, rng, interactive_hyperdrive)

# Open some trades
logging.info("Open random trades...")
trade_events = open_random_trades(trade_list, chain, rng, interactive_hyperdrive, advance_time=False)
trade_events = execute_random_trades(num_trades, chain, rng, interactive_hyperdrive, advance_time=False)

# Ensure all trades open are within the same checkpoint
trade_maturity_times = []
Expand Down
8 changes: 2 additions & 6 deletions lib/agent0/agent0/interactive_fuzz/fuzz_path_independence.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@
from agent0.interactive_fuzz.helpers import (
FuzzAssertionException,
close_random_trades,
execute_random_trades,
fp_isclose,
generate_trade_list,
open_random_trades,
setup_fuzz,
)

Expand Down Expand Up @@ -90,12 +89,9 @@ def fuzz_path_independence(
log_filename, chain_config, log_to_stdout, fees=False, var_interest=FixedPoint(0)
)

# Generate a list of agents that execute random trades
trade_list = generate_trade_list(num_trades, rng, interactive_hyperdrive)

# Open some trades
logging.info("Open random trades...")
trade_events = open_random_trades(trade_list, chain, rng, interactive_hyperdrive, advance_time=True)
trade_events = execute_random_trades(num_trades, chain, rng, interactive_hyperdrive, advance_time=True)
assert len(trade_events) > 0
agent = trade_events[0][0]

Expand Down
44 changes: 23 additions & 21 deletions lib/agent0/agent0/interactive_fuzz/fuzz_profit_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ def fuzz_profit_check(chain_config: LocalChain.Config | None = None, log_to_stdo
)

# Generate funded trading agent
long_agent = interactive_hyperdrive.init_agent(base=long_trade_amount * 2, eth=FixedPoint(100), name="alice")
long_agent = interactive_hyperdrive.init_agent(base=long_trade_amount, eth=FixedPoint(100), name="alice")
long_agent_initial_balance = long_agent.wallet.balance.amount
# Open a long
logging.info("Open a long...")
open_long_event = long_agent.open_long(base=long_trade_amount)
Expand Down Expand Up @@ -109,6 +110,7 @@ def fuzz_profit_check(chain_config: LocalChain.Config | None = None, log_to_stdo
# the short trade amount is in bonds, but we know we will need much less base
# we can play it safe by initializing with that much base
short_agent = interactive_hyperdrive.init_agent(base=short_trade_amount, eth=FixedPoint(100), name="bob")
short_agent_initial_balance = short_agent.wallet.balance.amount
# Set trade amount to the new wallet position (due to losing money from the previous open/close)
logging.info("Open a short...")
open_short_event = short_agent.open_short(bonds=short_trade_amount)
Expand All @@ -126,10 +128,12 @@ def fuzz_profit_check(chain_config: LocalChain.Config | None = None, log_to_stdo
# Ensure that the prior trades did not result in a profit
check_data = {
"long_trade_amount": long_trade_amount,
"short_trade_amount": short_trade_amount,
"long_agent": long_agent,
"short_agent": short_agent,
"long_agent_initial_balance": long_agent_initial_balance,
"long_agent_final_balance": long_agent.wallet.balance.amount,
"long_events": {"open": open_long_event, "close": close_long_event},
"short_trade_amount": short_trade_amount,
"short_agent_final_balance": short_agent.wallet.balance.amount,
"short_agent_initial_balance": short_agent_initial_balance,
"short_events": {"open": open_short_event, "close": close_short_event},
}
try:
Expand Down Expand Up @@ -265,9 +269,7 @@ def parse_arguments(argv: Sequence[str] | None = None) -> Args:
return namespace_to_args(parser.parse_args())


def invariant_check(
check_data: dict[str, Any],
) -> None:
def invariant_check(check_data: dict[str, Any]) -> None:
"""Check the pool state invariants.

Arguments
Expand All @@ -294,17 +296,17 @@ def invariant_check(
exception_data["invariance_check:long_base_amount_difference_in_wei"] = difference_in_wei
failed = True

agent_balance: FixedPoint = check_data["long_agent"].wallet.balance.amount
trade_amount: FixedPoint = check_data["long_trade_amount"]
if agent_balance >= trade_amount:
difference_in_wei = abs(agent_balance.scaled_value - trade_amount.scaled_value)
initial_agent_balance: FixedPoint = check_data["long_agent_initial_balance"]
final_agent_balance: FixedPoint = check_data["long_agent_final_balance"]
if final_agent_balance > initial_agent_balance:
difference_in_wei = abs(final_agent_balance.scaled_value - initial_agent_balance.scaled_value)
exception_message.append(
f"LONG: Agent made a profit when the should not have.\n"
f"{agent_balance=} should not be >= {trade_amount=}. "
f"{final_agent_balance=} should not be > {initial_agent_balance=}. "
f"{difference_in_wei=}"
)
exception_data["invariance_check:long_agent_balance"] = agent_balance
exception_data["invariance_check:long_trade_amount"] = trade_amount
exception_data["invariance_check:long_agent_initial_balance"] = initial_agent_balance
exception_data["invariance_check:long_agent_final_balance"] = final_agent_balance
exception_data["invariance_check:long_agent_balance_difference_in_wei"] = difference_in_wei
failed = True

Expand All @@ -323,17 +325,17 @@ def invariant_check(
exception_data["invariance_check:short_base_amount_difference_in_wei"] = difference_in_wei
failed = True

agent_balance: FixedPoint = check_data["short_agent"].wallet.balance.amount
trade_amount: FixedPoint = check_data["short_trade_amount"]
if agent_balance >= trade_amount:
difference_in_wei = abs(agent_balance.scaled_value - trade_amount.scaled_value)
initial_agent_balance: FixedPoint = check_data["short_agent_initial_balance"]
final_agent_balance: FixedPoint = check_data["short_agent_final_balance"]
if final_agent_balance > initial_agent_balance:
difference_in_wei = abs(initial_agent_balance.scaled_value - final_agent_balance.scaled_value)
exception_message.append(
f"SHORT: Agent made a profit when the should not have.\n"
f"{agent_balance=} should not be >= {trade_amount=}. "
f"{final_agent_balance=} should not be > {initial_agent_balance=}. "
f"{difference_in_wei=}"
)
exception_data["invariance_check:short_agent_balance"] = agent_balance
exception_data["invariance_check:short_trade_amount"] = trade_amount
exception_data["invariance_check:short_agent_initial_balance"] = initial_agent_balance
exception_data["invariance_check:short_agent_final_balance"] = final_agent_balance
exception_data["invariance_check:short_agent_balance_difference_in_wei"] = difference_in_wei
failed = True

Expand Down
3 changes: 1 addition & 2 deletions lib/agent0/agent0/interactive_fuzz/helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Shared functions for interactive fuzz testing."""

from .close_random_trades import close_random_trades
from .execute_random_trades import execute_random_trades
from .fuzz_assertion_exception import FuzzAssertionException, fp_isclose
from .generate_trade_list import generate_trade_list
from .open_random_trades import open_random_trades
from .setup_fuzz import setup_fuzz
161 changes: 161 additions & 0 deletions lib/agent0/agent0/interactive_fuzz/helpers/execute_random_trades.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""Return a list of results from opening a random longs and shorts."""
from __future__ import annotations

from typing import Literal, overload

import numpy as np
from fixedpointmath import FixedPoint
from numpy.random._generator import Generator

from agent0.hyperdrive.interactive import InteractiveHyperdrive, LocalChain
from agent0.hyperdrive.interactive.event_types import OpenLong, OpenShort
from agent0.hyperdrive.interactive.interactive_hyperdrive_agent import InteractiveHyperdriveAgent
from agent0.hyperdrive.state.hyperdrive_actions import HyperdriveActionType


def execute_random_trades(
num_trades: int,
chain: LocalChain,
rng: Generator,
interactive_hyperdrive: InteractiveHyperdrive,
advance_time: bool = False,
) -> list[tuple[InteractiveHyperdriveAgent, OpenLong | OpenShort]]:
"""Conduct some trades specified by the trade list.
If advance time is true, the sum of all time passed between all trades will be between 0 and the position duration.

Arguments
---------
num_trades: int
The number of trades to execute.
chain: LocalChain
An instantiated LocalChain.
rng: `Generator <https://numpy.org/doc/stable/reference/random/generator.html>`_
The numpy Generator provides access to a wide range of distributions, and stores the random state.
interactive_hyperdrive: InteractiveHyperdrive
An instantiated InteractiveHyperdrive object.
advance_time: bool, optional
If True, advance time a random amount between 0 and the position duration after each trade.
Defaults to False.

Returns
-------
list[tuple[InteractiveHyperdriveAgent, OpenLong | OpenShort]]
A list with an entry per trade, containing a tuple with:
- the agent executing the trade
- either the OpenLong or OpenShort trade event
"""
time_diffs = None
if advance_time:
# Generate the total time elapsed for all trades
max_advance_time = rng.integers(
low=0, high=interactive_hyperdrive.hyperdrive_interface.pool_config.position_duration
)
# Generate intermediate points between 0 and max_advance_time, sorted in ascending order
# Generating number of trades + 1 since cumulative diff results in one less
intermediate_points = np.sort(rng.integers(low=0, high=max_advance_time, size=num_trades + 1))
# Find cumulative differences of intermediate points for how much time to wait between each trade
time_diffs = np.diff(intermediate_points)

# Generate a list of trades
available_actions = np.array([HyperdriveActionType.OPEN_LONG, HyperdriveActionType.OPEN_SHORT])
# Do the trades
trade_events: list[tuple[InteractiveHyperdriveAgent, OpenLong | OpenShort]] = []
for trade_index, trade_type in enumerate([rng.choice(available_actions, size=1)[0] for _ in range(num_trades)]):
trade_amount = _get_open_trade_amount(trade_type, rng, interactive_hyperdrive)
# the short trade amount is technically bonds, but we know that will be less than the required base
agent = interactive_hyperdrive.init_agent(base=trade_amount, eth=FixedPoint(100))
trade_event = _execute_trade(trade_type, trade_amount, agent)
trade_events.append((agent, trade_event))
if advance_time:
# Advance a random amount of time between opening trades
assert time_diffs is not None
chain.advance_time(
time_diffs[trade_index],
create_checkpoints=True,
)
return trade_events


def _get_open_trade_amount(
trade_type: HyperdriveActionType,
rng: Generator,
interactive_hyperdrive: InteractiveHyperdrive,
max_budget: FixedPoint = FixedPoint("1e9"),
) -> FixedPoint:
"""Get a trade amount for a given trade and Hyperdrive pool.

Arguments
---------
trade_type: HyperdriveActionType
A trade to be executed on the Hyperdrive pool.
rng: `Generator <https://numpy.org/doc/stable/reference/random/generator.html>`_
The numpy Generator provides access to a wide range of distributions, and stores the random state.
interactive_hyperdrive: InteractiveHyperdrive
An instantiated InteractiveHyperdrive object.
max_budget: FixedPoint, optional
An optional amount to set an upper bound for the trade, defaults to FixedPoint("1e9").

Returns
-------
FixedPoint
The trade amount, bound by the min & max amount allowed.
"""
min_trade = interactive_hyperdrive.hyperdrive_interface.pool_config.minimum_transaction_amount
match trade_type:
case HyperdriveActionType.OPEN_LONG:
max_trade = interactive_hyperdrive.hyperdrive_interface.calc_max_long(
max_budget, interactive_hyperdrive.hyperdrive_interface.current_pool_state
)
case HyperdriveActionType.OPEN_SHORT:
max_trade = interactive_hyperdrive.hyperdrive_interface.calc_max_short(
max_budget, interactive_hyperdrive.hyperdrive_interface.current_pool_state
)
case _:
raise ValueError(f"Invalid {trade_type=}\nOnly opening trades are allowed.")
return FixedPoint(scaled_value=int(np.floor(rng.uniform(low=min_trade.scaled_value, high=max_trade.scaled_value))))


@overload
def _execute_trade(
trade_type: Literal[HyperdriveActionType.OPEN_LONG], trade_amount: FixedPoint, agent: InteractiveHyperdriveAgent
) -> OpenLong:
...


@overload
def _execute_trade(
trade_type: Literal[HyperdriveActionType.OPEN_SHORT], trade_amount: FixedPoint, agent: InteractiveHyperdriveAgent
) -> OpenShort:
...


def _execute_trade(
trade_type: HyperdriveActionType, trade_amount: FixedPoint, agent: InteractiveHyperdriveAgent
) -> OpenLong | OpenShort:
"""Execute a trade given the type, amount, and agent.

Arguments
---------
trade_type: HyperdriveActionType
A trade to be executed on the Hyperdrive pool.
Must be open long or open short.
trade_amount: FixedPoint
A valid (between pool and agent wallet min & max) amount of base or bonds to be traded.
The unit changes depending on the trade type, where long uses base and short uses bonds.
agent: InteractiveHyperdriveAgent
An agent to use for executing the trade.
It must have enough base in its wallet.

Returns
-------
OpenLong | OpenShort
The receipt for the given trade.
"""
match trade_type:
case HyperdriveActionType.OPEN_LONG:
trade_event = agent.open_long(base=trade_amount)
case HyperdriveActionType.OPEN_SHORT:
trade_event = agent.open_short(bonds=trade_amount)
case _:
raise ValueError(f"{trade_type=} is not supported.")
return trade_event
Loading
Loading