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

add expiration logs #99

1 change: 1 addition & 0 deletions pams/logs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .base import CancelLog
from .base import ExecutionLog
from .base import ExpirationLog
from .base import Log
from .base import Logger
from .base import MarketStepBeginLog
Expand Down
58 changes: 58 additions & 0 deletions pams/logs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,51 @@ def __init__(
# TODO: Type validation


class ExpirationLog(Log):
"""Expiration type log class.

This log is usually generated when an order is expired on markets.
"""

def __init__(
self,
order_id: Optional[int],
market_id: int,
time: int,
order_time: Optional[int],
agent_id: int,
is_buy: bool,
kind: OrderKind,
volume: int,
price: Optional[float] = None,
ttl: Optional[int] = None,
):
"""initialize

Args:
order_id (int): order ID.
market_id (int): market ID.
time (int): time.
order_time (int): time to order.
agent_id (int): agent ID.
is_buy (bool): whether it is a buy order or not.
kind (:class:`pams.order.OrderKind`): kind of order.
volume (int): order volume.
price (float, Optional): order price.
ttl (int, Optional): time to order expiration.
"""
self.order_id: Optional[int] = order_id
self.market_id: int = market_id
self.time: int = time
self.order_time: Optional[int] = order_time
self.agent_id: int = agent_id
self.is_buy: bool = is_buy
self.kind: OrderKind = kind
self.price: Optional[float] = price
self.volume: int = volume
self.ttl: Optional[int] = ttl


class ExecutionLog(Log):
"""Execution type log class.

Expand Down Expand Up @@ -352,6 +397,8 @@ def process(self, logs: List["Log"]) -> None:
self.process_order_log(log=log)
elif isinstance(log, CancelLog):
self.process_cancel_log(log=log)
elif isinstance(log, ExpirationLog):
self.process_expiration_log(log=log)
elif isinstance(log, ExecutionLog):
self.process_execution_log(log=log)
elif isinstance(log, SimulationBeginLog):
Expand Down Expand Up @@ -390,6 +437,17 @@ def process_cancel_log(self, log: "CancelLog") -> None:
"""
pass

def process_expiration_log(self, log: "ExpirationLog") -> None:
"""process expiration log. Called from :func:`process`.

Args:
log (:class:`pams.logs.ExpirationLog`]): expiration log

Returns:
None
"""
pass

def process_execution_log(self, log: "ExecutionLog") -> None:
"""process execution log. Called from :func:`process`.

Expand Down
21 changes: 17 additions & 4 deletions pams/market.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from .logs.base import CancelLog
from .logs.base import ExecutionLog
from .logs.base import ExpirationLog
from .logs.base import Log
from .logs.base import Logger
from .logs.base import OrderLog
Expand Down Expand Up @@ -551,8 +552,14 @@ def _set_time(self, time: int, next_fundamental_price: float) -> None:
None
"""
self.time = time
self.buy_order_book._set_time(time)
self.sell_order_book._set_time(time)
logs: List[ExpirationLog] = self.buy_order_book._set_time(time)
if self.logger is not None:
for log in logs:
log.read_and_write(logger=self.logger)
logs_: List[ExpirationLog] = self.sell_order_book._set_time(time)
if self.logger is not None:
for log_ in logs_:
log_.read_and_write(logger=self.logger)
self._fill_until(time=time)
self._fundamental_prices[self.time] = next_fundamental_price
if self.time > 0:
Expand Down Expand Up @@ -599,8 +606,14 @@ def _update_time(self, next_fundamental_price: float) -> None:
None
"""
self.time += 1
self.buy_order_book._set_time(self.time)
self.sell_order_book._set_time(self.time)
logs: List[ExpirationLog] = self.buy_order_book._set_time(self.time)
if self.logger is not None:
for log in logs:
log.read_and_write(logger=self.logger)
logs_: List[ExpirationLog] = self.sell_order_book._set_time(self.time)
if self.logger is not None:
for log_ in logs_:
log_.read_and_write(logger=self.logger)
self._fill_until(time=self.time)
self._fundamental_prices[self.time] = next_fundamental_price
if self.time > 0:
Expand Down
33 changes: 27 additions & 6 deletions pams/order_book.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import List
from typing import Optional

from .logs.base import ExpirationLog
from .order import Cancel
from .order import Order

Expand Down Expand Up @@ -122,35 +123,55 @@ def change_order_volume(self, order: Order, delta: int) -> None:
if order.volume < 0:
raise AssertionError

def _check_expired_orders(self) -> None:
"""check and delete expired orders. (Internal Method)"""
def _check_expired_orders(self) -> List[ExpirationLog]:
"""check and delete expired orders. (Internal Method)

Returns:
List[ExpirationLog]: the list of expiration logs.
"""
delete_orders: List[Order] = sum(
[value for key, value in self.expire_time_list.items() if key < self.time],
[],
)
delete_keys: List[int] = [
key for key, value in self.expire_time_list.items() if key < self.time
]
logs: List[ExpirationLog] = []
if len(delete_orders) == 0:
return
return logs
# TODO: think better sorting in the following 3 lines
for delete_order in delete_orders:
log: ExpirationLog = ExpirationLog(
order_id=delete_order.order_id,
market_id=delete_order.market_id,
time=self.time,
order_time=delete_order.placed_at,
agent_id=delete_order.agent_id,
is_buy=delete_order.is_buy,
kind=delete_order.kind,
volume=delete_order.volume,
price=delete_order.price,
ttl=delete_order.ttl,
)
logs.append(log)
self.priority_queue.remove(delete_order)
heapq.heapify(self.priority_queue)
for key in delete_keys:
self.expire_time_list.pop(key)
return logs

def _set_time(self, time: int) -> None:
def _set_time(self, time: int) -> List[ExpirationLog]:
"""set time step. (Usually, it is called from market.)

Args:
time (int): time step.

Returns:
None
List[ExpirationLog]: the list of expiration logs.
"""
self.time = time
self._check_expired_orders()
logs: List[ExpirationLog] = self._check_expired_orders()
return logs

def _update_time(self) -> None:
"""update time. (Usually, it is called from market.)
Expand Down
79 changes: 79 additions & 0 deletions tests/pams/logs/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pams import Simulator
from pams.logs import CancelLog
from pams.logs import ExecutionLog
from pams.logs import ExpirationLog
from pams.logs import Log
from pams.logs import Logger
from pams.logs import MarketStepBeginLog
Expand Down Expand Up @@ -140,6 +141,53 @@ def test__init__(self) -> None:
assert log.ttl is None


class TestExpirationLog:
def test__init__(self) -> None:
log = ExpirationLog(
order_id=2,
market_id=3,
time=10,
order_time=8,
agent_id=4,
is_buy=True,
kind=LIMIT_ORDER,
volume=10,
price=100.0,
ttl=2,
)
assert log.order_id == 2
assert log.market_id == 3
assert log.time == 10
assert log.order_time == 8
assert log.agent_id == 4
assert log.is_buy is True
assert log.kind == LIMIT_ORDER
assert log.volume == 10
assert log.price == 100.0
assert log.ttl == 2

log = ExpirationLog(
order_id=2,
market_id=3,
time=10,
order_time=8,
agent_id=4,
is_buy=True,
kind=MARKET_ORDER,
volume=10,
)
assert log.order_id == 2
assert log.market_id == 3
assert log.time == 10
assert log.order_time == 8
assert log.agent_id == 4
assert log.is_buy is True
assert log.kind == MARKET_ORDER
assert log.volume == 10
assert log.price is None
assert log.ttl is None


class TestExecutionLog:
def test___init__(self) -> None:
log = ExecutionLog(
Expand Down Expand Up @@ -329,6 +377,7 @@ def __init__(self) -> None:
super().__init__()
self.n_order_log = 0
self.n_cancel_log = 0
self.n_expiration_log = 0
self.n_execution_log = 0
self.n_simulation_begin_log = 0
self.n_simulation_end_log = 0
Expand All @@ -343,6 +392,9 @@ def process_order_log(self, log: OrderLog) -> None:
def process_cancel_log(self, log: CancelLog) -> None:
self.n_cancel_log += 1

def process_expiration_log(self, log: ExpirationLog) -> None:
self.n_expiration_log += 1

def process_execution_log(self, log: ExecutionLog) -> None:
self.n_execution_log += 1

Expand Down Expand Up @@ -400,6 +452,19 @@ def process_market_step_end_log(self, log: MarketStepEndLog) -> None:
ttl=9,
)
logger.write(log=cancel_log)
expiration_log = ExpirationLog(
order_id=1,
market_id=2,
time=5,
order_time=3,
agent_id=4,
is_buy=True,
kind=LIMIT_ORDER,
volume=6,
price=7.0,
ttl=2,
)
logger.write(log=expiration_log)
execution_log = ExecutionLog(
market_id=1,
time=2,
Expand Down Expand Up @@ -435,6 +500,7 @@ def process_market_step_end_log(self, log: MarketStepEndLog) -> None:

assert logger.n_order_log == 1
assert logger.n_cancel_log == 1
assert logger.n_expiration_log == 1
assert logger.n_execution_log == 1
assert logger.n_simulation_begin_log == 1
assert logger.n_simulation_end_log == 1
Expand Down Expand Up @@ -481,6 +547,19 @@ def test_process2(self) -> None:
ttl=9,
)
logger.write(log=cancel_log)
expiration_log = ExpirationLog(
order_id=1,
market_id=2,
time=5,
order_time=3,
agent_id=4,
is_buy=True,
kind=LIMIT_ORDER,
volume=6,
price=7.0,
ttl=2,
)
logger.write(log=expiration_log)
execution_log = ExecutionLog(
market_id=1,
time=2,
Expand Down
Loading