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

Refactoring of Trader #221

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
6 changes: 3 additions & 3 deletions experiments/simulation_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
from model.constants import blocks_per_day, blocks_per_year

BLOCKS_PER_TIMESTEP = 1 # number of blocks per timestep (=1 if sim on per-block-basis)
SIMULATION_TIME_DAYS = 2 # number of days
SIMULATION_TIME_DAYS = 1 # number of days
# number of simulation_configuration timesteps
TIMESTEPS = SIMULATION_TIME_DAYS * blocks_per_day // BLOCKS_PER_TIMESTEP
TIMESTEPS_PER_YEAR = blocks_per_year // BLOCKS_PER_TIMESTEP
MONTE_CARLO_RUNS = 2 # number of runs
DATA_SOURCE = 'historical' # 'mock' or 'historical'
MONTE_CARLO_RUNS = 1 # number of runs
DATA_SOURCE = 'mock' # 'mock' or 'historical'
TOTAL_BLOCKS = (BLOCKS_PER_TIMESTEP * TIMESTEPS) + 1
7 changes: 4 additions & 3 deletions model/entities/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,26 @@
# pylint: disable=too-few-public-methods
from typing import TYPE_CHECKING
from uuid import UUID
from model.entities.balance import Balance
if TYPE_CHECKING:
from model.entities.balance import Balance
from model.generators.accounts import AccountGenerator


class Account():
"""
Agent / Account Holder
"""
parent: "AccountGenerator"
account_id: UUID
account_name: str
balance: Balance
balance: "Balance"

def __init__(
self,
parent: "AccountGenerator",
account_id: UUID,
account_name: str,
balance: Balance
balance: "Balance"
):
self.parent = parent
self.account_id = account_id
Expand Down
49 changes: 17 additions & 32 deletions model/entities/strategies/strategy_arbitrage_trader.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
"""
Strategy: Arbitrage Trader
"""
from enum import Enum
import numpy as np

from model.types.base import MentoBuckets
from .trader_strategy import TraderStrategy


class TradingRegime(Enum):
SELL_STABLE = "SELL_STABLE"
SELL_RESERVE_ASSET = "SELL_RESERVE_ASSET"
PASS = "PASS"
from .trader_strategy import TradingRegime


# pylint: disable=using-constant-test
Expand All @@ -28,9 +22,9 @@ def __init__(self, parent, acting_frequency=1):

def sell_reserve_asset(self, _params, prev_state):
# Arb trade will sell CELO if CELO/USD > CELO/cUSD
return self.trading_regime(prev_state) == TradingRegime.SELL_RESERVE_ASSET
return self.determine_trading_regime(prev_state) == TradingRegime.SELL_RESERVE_ASSET

def trading_regime(self, prev_state) -> TradingRegime:
def determine_trading_regime(self, prev_state) -> TradingRegime:
"""
Indicates how the trader will act depending on the relation of mento price
and market price
Expand Down Expand Up @@ -63,7 +57,7 @@ def define_expressions(self, _params, prev_state):
mento_buckets = self.mento_buckets(prev_state)
spread = self.exchange_config.spread

if self.trading_regime(prev_state) == TradingRegime.SELL_STABLE:
if self.trading_regime == TradingRegime.SELL_STABLE:
self.expressions["profit"] = (
-1 * self.variables["sell_amount"]
* mento_buckets['reserve_asset']
Expand All @@ -72,7 +66,7 @@ def define_expressions(self, _params, prev_state):
+ (1 - spread) * self.variables["sell_amount"]
+ market_price * self.variables["sell_amount"]
)
elif self.trading_regime(prev_state) == TradingRegime.SELL_RESERVE_ASSET:
elif self.trading_regime == TradingRegime.SELL_RESERVE_ASSET:
self.expressions["profit"] = (
-self.variables["sell_amount"]
* mento_buckets['stable']
Expand All @@ -83,7 +77,7 @@ def define_expressions(self, _params, prev_state):
)

def trader_passes_step(self, _params, prev_state):
return (self.trading_regime(prev_state) == "PASS") or \
return (self.determine_trading_regime(prev_state) == "PASS") or \
(prev_state["timestep"] % self.acting_frequency != 0)

# # pylint: disable=attribute-defined-outside-init
Expand All @@ -94,23 +88,21 @@ def calculate(self, _params, prev_state):
market_price = self.market_price(prev_state)
mento_buckets = self.mento_buckets(prev_state)
spread = self.exchange_config.spread
mento_price = mento_buckets['stable'] / mento_buckets['reserve_asset']

if market_price * (1 - spread) > mento_price:
if self.trading_regime == TradingRegime.SELL_STABLE:
self.sell_order_stable(
mento_buckets,
market_price,
spread
)
elif market_price / (1 - spread) < mento_price:
elif self.trading_regime == TradingRegime.SELL_RESERVE_ASSET:
self.sell_order_reserve_asset(
mento_buckets,
market_price,
spread
)
else:
self.order = {"sell_amount": None}
self.sell_amount = self.order["sell_amount"]

# self.sell_amount = self.order["sell_amount"]

def sell_order_stable(self, buckets: MentoBuckets, market_price: float, spread: float):
"""
Expand All @@ -127,13 +119,9 @@ def sell_order_stable(self, buckets: MentoBuckets, market_price: float, spread:
spread
)

self.order = {
"sell_amount": min(
sell_amount,
max_budget_stable,
),
"sell_reserve_asset": False,
}
self.order.sell_amount = min(sell_amount,
max_budget_stable,)
self.order.sell_reserve_asset = False

def sell_order_reserve_asset(
self,
Expand All @@ -154,15 +142,12 @@ def sell_order_reserve_asset(
spread
)

self.order = {
"sell_amount": min(
sell_amount,
max_budget_celo,
),
"sell_reserve_asset": True,
}
self.order.sell_amount = min(sell_amount,
max_budget_celo,)
self.order.sell_reserve_asset = True

# pylint: disable=no-self-use

def optimal_sell_amount(self, bucket_sell, bucket_buy, price_buy_sell, spread):
"""
bucket_sell: bucket of asset that is sold
Expand Down
54 changes: 35 additions & 19 deletions model/entities/strategies/strategy_random_trader.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
"""
Strategy: Random Trader
"""
from typing import List
from cvxpy import Variable
import numpy as np

from experiments import simulation_configuration

from .trader_strategy import TraderStrategy
from model.entities.strategies.trader_strategy import TraderStrategy, TradingRegime
from model.types.order import Order


class RandomTrading(TraderStrategy):
"""
Random Trading
"""
order_list: List[Order]

def __init__(self, parent, acting_frequency=1):
# The following is used to define the strategy and needs to be provided in subclass
super().__init__(parent, acting_frequency)
self.generate_sell_amounts()
self.sell_amount = None
self.rng = parent.rngp.get_rng("RandomTrader", self.parent.account_id)
self.order_list = self.generate_sell_amounts()

def sell_reserve_asset(self, _params, prev_state):
return self.orders[prev_state["timestep"]]["sell_reserve_asset"]
return self.order_list["sell_reserve_asset"][prev_state["timestep"]]

def define_variables(self):
self.variables["sell_amount"] = Variable(pos=True)
Expand Down Expand Up @@ -52,39 +55,52 @@ def define_constraints(self, params, prev_state):
self.variables["sell_amount"]
<= min(
max_budget_reserve_asset,
self.orders[prev_state["timestep"]]["sell_amount"]
self.order_list["sell_amount"][prev_state["timestep"]]
)
)
else:
self.constraints.append(
self.variables["sell_amount"]
<= min(
max_budget_stable,
self.orders[prev_state["timestep"]]["sell_amount"]
self.order_list["sell_amount"][prev_state["timestep"]]
)
)

def determine_trading_regime(self, prev_state) -> TradingRegime:
"""
Indicates how the trader will act depending on the relation of mento price
and market price
"""
sell_reserve_asset = self.order_list[
'sell_reserve_asset'][prev_state['timestep']]
regime = TradingRegime.PASS
if sell_reserve_asset:
regime = TradingRegime.SELL_RESERVE_ASSET
elif not sell_reserve_asset:
regime = TradingRegime.SELL_STABLE
return regime

def generate_sell_amounts(
self,
blocks_per_timestep=simulation_configuration.BLOCKS_PER_TIMESTEP,
timesteps=simulation_configuration.TIMESTEPS,
):
"""
This function generates lognormal returns
"""
# timesteps_per_year = constants.blocks_per_year // blocks_per_timestep
sample_size = timesteps * blocks_per_timestep + 1
# TODO parametrise random params incl. seed
sell_gold = self.rng.binomial(1, 0.5, sample_size)
orders = np.vstack(
[sell_gold, np.abs(self.rng.normal(100, 5, size=sample_size))]
)
self.orders = np.core.records.fromarrays(
orders, names=["sell_reserve_asset", "sell_amount"]
)
sample_size = simulation_configuration.TOTAL_BLOCKS + 1

sell_reserve_asset = (self.rng.binomial(1, 0.5, sample_size) == 1)
sell_amount = np.abs(self.rng.normal(1000, 0, size=sample_size))

orders = {"sell_reserve_asset": sell_reserve_asset,
"sell_amount": sell_amount}
return orders

def calculate(self, _params, prev_state):
"""
Calculates optimal trade if analytical solution is available
"""
self.sell_amount = self.orders[prev_state["timestep"]]["sell_amount"]
sell_amounts = self.order_list["sell_amount"]
order_directions = self.order_list["sell_reserve_asset"]
self.order.sell_amount = sell_amounts[prev_state["timestep"]]
self.order.sell_reserve_asset = order_directions[prev_state["timestep"]]
69 changes: 42 additions & 27 deletions model/entities/strategies/trader_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,26 @@
inside of solve() but the
objective_function and the constraints should still be specified for completeness!
"""
from enum import Enum
from typing import TYPE_CHECKING
import logging
from cvxpy import Maximize, Minimize, Problem, Variable
import cvxpy

from model.types.base import MentoBuckets
from model.types.pair import Pair
from model.types.order import Order
from model.types.configs import MentoExchangeConfig
if TYPE_CHECKING:
from model.entities.trader import Trader


class TradingRegime(Enum):
SELL_STABLE = "SELL_STABLE"
SELL_RESERVE_ASSET = "SELL_RESERVE_ASSET"
PASS = "PASS"


class TraderStrategy:
"""
Base trader strategy class to solve a convex optimisation problem.
Expand All @@ -28,6 +36,8 @@ class TraderStrategy:
"""
parent: "Trader"
exchange_config: MentoExchangeConfig
trading_regime: TradingRegime
order: Order

def __init__(self, parent: "Trader", acting_frequency):
self.parent = parent
Expand All @@ -41,9 +51,13 @@ def __init__(self, parent: "Trader", acting_frequency):
self.objective_function = None
self.optimization_direction = None
self.constraints = []
# TODO order vs sell_amount ???
self.sell_amount = None
self.order = None
self.trading_regime = None
self.order = Order(account_id=self.parent.account_id,
pair=None,
sell_amount=None,
buy_amount=None,
sell_reserve_asset=None,
exchange=self.parent.config.exchange)

@property
def reference_fiat(self):
Expand Down Expand Up @@ -79,6 +93,12 @@ def define_expressions(self, _params, _prev_state):
"""
raise NotImplementedError("Subclasses must implement define_expressions()")

def determine_trading_regime(self, _prev_state) -> TradingRegime:
"""
Indicates how the trader will act depending on the state of the market
"""
raise NotImplementedError("Subclasses must implement trading_regime()")

def define_constraints(self, params, prev_state):
"""
Defines and returns the constraints under which the optimization is conducted
Expand Down Expand Up @@ -167,41 +187,36 @@ def minimise_price_impact(self, sell_amount, sell_reserve_asset, params):
return sell_amount_adjusted

def trader_passes_step(self, _params, prev_state):
"""
evaluates whether trader passes current step due to acting frequency
"""
return prev_state["timestep"] % self.acting_frequency != 0

def return_optimal_trade(self, params, prev_state):
def create_order(self, params, prev_state):
"""
Returns the optimal action to be executed by actor
"""
if self.trader_passes_step(params, prev_state):
# Actor not acting this timestep
trade = None
else:
self.determine_trading_regime(prev_state)
if (not self.trader_passes_step(params, prev_state)
and self.trading_regime != TradingRegime.PASS):
self.optimize(params=params, prev_state=prev_state)
sell_amount = (
self.variables["sell_amount"].value
if self.variables
else self.sell_amount
self.order.sell_amount = (
self.variables["sell_amount"].value if self.variables else self.order.sell_amount
)
if sell_amount is None or sell_amount == 0:
trade = None

if self.order.sell_amount is None or self.order.sell_amount == 0:
self.order.sell_amount = None
else:
sell_reserve_asset = self.sell_reserve_asset(params, prev_state)
sell_amount = self.minimise_price_impact(
sell_amount, sell_reserve_asset, params)
buy_amount = self.mento.get_buy_amount(
#self.order.pair = self.sell_reserve_asset(params, prev_state)
self.order.sell_amount = self.minimise_price_impact(
self.order.sell_amount, self.order.pair, params)
self.order.buy_amount = self.mento.get_buy_amount(
exchange=self.parent.config.exchange,
sell_amount=sell_amount,
sell_reserve_asset=sell_reserve_asset,
sell_amount=self.order.sell_amount,
sell_reserve_asset=self.order.sell_reserve_asset,
prev_state=prev_state,
)

trade = {
"sell_reserve_asset": sell_reserve_asset,
"sell_amount": sell_amount,
"buy_amount": buy_amount,
}
return trade
self.parent.order = self.order

def market_price(self, prev_state) -> float:
# TODO: Do we need to quote in equivalent Fiat for Stable?
Expand Down
Loading