diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index c4dd317b..541d276d 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -11,20 +11,20 @@ jobs: runs-on: ubuntu-latest if: ${{ contains(github.ref, 'release/') }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - name: Install poetry - run: pip install poetry - - name: Bump version - run: git branch --show-current | sed 's|release/||' | xargs poetry version | { printf '::set-output name=PR_TITLE::'; cat; } - id: bump - - name: Bump version 2 - run: git branch --show-current | sed 's|release/||' | xargs -I {} echo '__version__ = "{}"' > pams/version.py - - name: Create pull request - uses: peter-evans/create-pull-request@v3 - with: - author: GitHub Actions - commit-message: ${{ steps.bump.outputs.PR_TITLE }} - delete-branch: true - branch-suffix: short-commit-hash - title: ${{ steps.bump.outputs.PR_TITLE }} + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - name: Install poetry + run: pip install poetry + - name: Bump version + run: git branch --show-current | sed 's|release/||' | xargs poetry version | { printf '::set-output name=PR_TITLE::'; cat; } + id: bump + - name: Bump version 2 + run: git branch --show-current | sed 's|release/||' | xargs -I {} echo '__version__ = "{}"' > pams/version.py + - name: Create pull request + uses: peter-evans/create-pull-request@v3 + with: + author: GitHub Actions + commit-message: ${{ steps.bump.outputs.PR_TITLE }} + delete-branch: true + branch-suffix: short-commit-hash + title: ${{ steps.bump.outputs.PR_TITLE }} diff --git a/.github/workflows/ci-python-min.yml b/.github/workflows/ci-python-min.yml index afb3a9a6..b426f84f 100644 --- a/.github/workflows/ci-python-min.yml +++ b/.github/workflows/ci-python-min.yml @@ -6,9 +6,9 @@ name: CI-min on: # Triggers the workflow on push or pull request events but only for the main branch push: - branches: [ dev, release/* ] + branches: [dev, release/*] pull_request: - branches: [ dev, release/* ] + branches: [dev, release/*] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -18,12 +18,12 @@ jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on - runs-on: ${{ matrix.platform }} + runs-on: ${{ matrix.platform }} strategy: max-parallel: 5 matrix: platform: [ubuntu-latest] - python-version: [3.9] + python-version: [3.11] # Steps represent a sequence of tasks that will be executed as part of the job steps: diff --git a/.github/workflows/ci-python-notebooks.yml b/.github/workflows/ci-python-notebooks.yml index f2b5ca26..b1f0b1ae 100644 --- a/.github/workflows/ci-python-notebooks.yml +++ b/.github/workflows/ci-python-notebooks.yml @@ -6,9 +6,9 @@ name: CI-notebooks on: # Triggers the workflow on push or pull request events but only for the main branch push: - branches: [ main, dev, release/* ] + branches: [main, dev, release/*] pull_request: - branches: [ main, dev, release/* ] + branches: [main, dev, release/*] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -18,12 +18,12 @@ jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on - runs-on: ${{ matrix.platform }} + runs-on: ${{ matrix.platform }} strategy: max-parallel: 5 matrix: platform: [ubuntu-latest] - python-version: [3.8] + python-version: [3.11] # Steps represent a sequence of tasks that will be executed as part of the job steps: diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index bc8dfcfa..1f831400 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -6,9 +6,9 @@ name: CI on: # Triggers the workflow on push or pull request events but only for the main branch push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -18,7 +18,7 @@ jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on - runs-on: ${{ matrix.platform }} + runs-on: ${{ matrix.platform }} strategy: max-parallel: 5 matrix: @@ -47,4 +47,4 @@ jobs: - name: Doc Test if: matrix.platform == 'ubuntu-latest' run: | - poetry run pytest --doctest-modules pams + poetry run pytest --doctest-modules pams diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 18baae52..2c1bd3de 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -6,9 +6,9 @@ name: codecov CI on: # Triggers the workflow on push or pull request events but only for the main branch push: - branches: [ main, dev, release/* ] + branches: [main, dev, release/*] pull_request: - branches: [ main ] + branches: [main] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -21,26 +21,26 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: [3.11] # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2.2.2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools wheel - python -m pip install poetry - poetry install - - name: make cov report - run: | - poetry run pytest --cov=./ --cov-report=xml - - uses: codecov/codecov-action@v2 - with: - token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos - files: ./coverage.xml - flags: pytest - verbose: true + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2.2.2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + python -m pip install poetry + poetry install + - name: make cov report + run: | + poetry run pytest --cov=./ --cov-report=xml + - uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos + files: ./coverage.xml + flags: pytest + verbose: true diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 57889af4..cc4cd646 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -8,12 +8,12 @@ on: jobs: doc: - runs-on: ${{ matrix.platform }} + runs-on: ${{ matrix.platform }} strategy: max-parallel: 5 matrix: platform: [ubuntu-latest] - python-version: [3.9] + python-version: [3.11] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 37cd92c3..ee221981 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,36 +1,36 @@ name: Release on: - pull_request: - branches: - - main - types: - - closed + pull_request: + branches: + - main + types: + - closed jobs: tagging: runs-on: ubuntu-latest if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') steps: - - name: Get the version - id: get_version - run: | - echo ::set-output name=TAG::`echo $SOURCE_VERSION | cut -d / -f 2` - env: - SOURCE_VERSION: ${{ github.event.pull_request.head.ref }} - - name: Echo tag - run: echo ${{ steps.get_version.outputs.TAG }} - - uses: actions/checkout@v2 - - run: | - git config --global user.email "masa.hirano.1996@gmail.com" - git config --global user.name "Masanori HIRANO" - git fetch origin ${{ github.event.pull_request.head.ref }} - git checkout main - git tag -a `echo '${{ github.event.pull_request.head.ref }}' | sed 's/release\///'` -m " " - git push origin `echo '${{ github.event.pull_request.head.ref }}' | sed 's/release\///'` + - name: Get the version + id: get_version + run: | + echo ::set-output name=TAG::`echo $SOURCE_VERSION | cut -d / -f 2` + env: + SOURCE_VERSION: ${{ github.event.pull_request.head.ref }} + - name: Echo tag + run: echo ${{ steps.get_version.outputs.TAG }} + - uses: actions/checkout@v2 + - run: | + git config --global user.email "masa.hirano.1996@gmail.com" + git config --global user.name "Masanori HIRANO" + git fetch origin ${{ github.event.pull_request.head.ref }} + git checkout main + git tag -a `echo '${{ github.event.pull_request.head.ref }}' | sed 's/release\///'` -m " " + git push origin `echo '${{ github.event.pull_request.head.ref }}' | sed 's/release\///'` test: # The type of runner that the job will run on name: final test - runs-on: ${{ matrix.platform }} + runs-on: ${{ matrix.platform }} needs: tagging strategy: max-parallel: 5 @@ -57,7 +57,7 @@ jobs: poetry run pflake8 . poetry run mypy . poetry run pytest tests/ - + releasetest: # The type of runner that the job will run on name: release test @@ -65,32 +65,32 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: [3.11] # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2.2.2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools wheel - python -m pip install poetry - poetry install - - name: test release - run: | - poetry config repositories.testpypi https://test.pypi.org/legacy/ - poetry publish --build -r testpypi --username __token__ --password ${{ secrets.TEST_PYPI_TOKEN }} - + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2.2.2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + python -m pip install poetry + poetry install + - name: test release + run: | + poetry config repositories.testpypi https://test.pypi.org/legacy/ + poetry publish --build -r testpypi --username __token__ --password ${{ secrets.TEST_PYPI_TOKEN }} + releasetestcheck: name: release test check needs: releasetest runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: [3.11] # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -117,82 +117,82 @@ jobs: - name: Test run: | pytest tests/ - + release: name: release needs: [releasetestcheck, tagging] runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: [3.11] # Steps represent a sequence of tasks that will be executed as part of the job steps: - - name: Get the version - id: get_version - run: | - echo ::set-output name=TAG::`echo $SOURCE_VERSION | cut -d / -f 2` - env: - SOURCE_VERSION: ${{ github.event.pull_request.head.ref }} - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2.2.2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools wheel - python -m pip install poetry - poetry install - - name: release - run: | - poetry publish --build --username __token__ --password ${{ secrets.PYPI_TOKEN }} - - name: Create release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.get_version.outputs.TAG }} - release_name: ${{ steps.get_version.outputs.TAG }} - draft: false - prerelease: false - body: | - ${{ github.event.pull_request.body }} - - This release is automatically generated. - Please see the pull request. - [${{ github.event.pull_request.html_url }}](${{ github.event.pull_request.html_url }}) - - name: Generate checksum - run: | - cd dist; sha256sum * > checksums.txt; cd - - - name: Upload tar.gz - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: dist/pams-${{ steps.get_version.outputs.TAG }}.tar.gz - asset_name: pams-${{ steps.get_version.outputs.TAG }}.tar.gz - asset_content_type: application/gzip - - name: Upload whl - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: dist/pams-${{ steps.get_version.outputs.TAG }}-py3-none-any.whl - asset_name: pams-${{ steps.get_version.outputs.TAG }}-py3-none-any.whl - asset_content_type: application/x-pywheel+zip - - name: Upload checksum - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: dist/checksums.txt - asset_name: pams-${{ steps.get_version.outputs.TAG }}-checksums.txt - asset_content_type: text/plain - - name: remove branch - run: | - git push --delete origin ${{ github.event.pull_request.head.ref }} + - name: Get the version + id: get_version + run: | + echo ::set-output name=TAG::`echo $SOURCE_VERSION | cut -d / -f 2` + env: + SOURCE_VERSION: ${{ github.event.pull_request.head.ref }} + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2.2.2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + python -m pip install poetry + poetry install + - name: release + run: | + poetry publish --build --username __token__ --password ${{ secrets.PYPI_TOKEN }} + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.get_version.outputs.TAG }} + release_name: ${{ steps.get_version.outputs.TAG }} + draft: false + prerelease: false + body: | + ${{ github.event.pull_request.body }} + + This release is automatically generated. + Please see the pull request. + [${{ github.event.pull_request.html_url }}](${{ github.event.pull_request.html_url }}) + - name: Generate checksum + run: | + cd dist; sha256sum * > checksums.txt; cd - + - name: Upload tar.gz + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/pams-${{ steps.get_version.outputs.TAG }}.tar.gz + asset_name: pams-${{ steps.get_version.outputs.TAG }}.tar.gz + asset_content_type: application/gzip + - name: Upload whl + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/pams-${{ steps.get_version.outputs.TAG }}-py3-none-any.whl + asset_name: pams-${{ steps.get_version.outputs.TAG }}-py3-none-any.whl + asset_content_type: application/x-pywheel+zip + - name: Upload checksum + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/checksums.txt + asset_name: pams-${{ steps.get_version.outputs.TAG }}-checksums.txt + asset_content_type: text/plain + - name: remove branch + run: | + git push --delete origin ${{ github.event.pull_request.head.ref }} diff --git a/pams/logs/__init__.py b/pams/logs/__init__.py index 2f4826b2..08c62832 100644 --- a/pams/logs/__init__.py +++ b/pams/logs/__init__.py @@ -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 diff --git a/pams/logs/base.py b/pams/logs/base.py index 134dbbdd..6b722330 100644 --- a/pams/logs/base.py +++ b/pams/logs/base.py @@ -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. @@ -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): @@ -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`. diff --git a/pams/market.py b/pams/market.py index 3b9d7936..57a7f509 100644 --- a/pams/market.py +++ b/pams/market.py @@ -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 @@ -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: @@ -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: diff --git a/pams/order_book.py b/pams/order_book.py index 8f3413d5..341ab56e 100644 --- a/pams/order_book.py +++ b/pams/order_book.py @@ -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 @@ -122,8 +123,12 @@ 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], [], @@ -131,26 +136,42 @@ def _check_expired_orders(self) -> None: 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.) diff --git a/pams/version.py b/pams/version.py index d3ec452c..3ced3581 100644 --- a/pams/version.py +++ b/pams/version.py @@ -1 +1 @@ -__version__ = "0.2.0" +__version__ = "0.2.1" diff --git a/pyproject.toml b/pyproject.toml index 88bfe329..6a172774 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pams" -version = "0.2.0" +version = "0.2.1" description = "PAMS: Platform for Artificial Market Simulations" authors = ["Masanori HIRANO ", "Ryosuke TAKATA ", "Kiyoshi IZUMI"] license = "epl-1.0" diff --git a/tests/pams/logs/test_base.py b/tests/pams/logs/test_base.py index dd3f0fd7..7410a8b9 100644 --- a/tests/pams/logs/test_base.py +++ b/tests/pams/logs/test_base.py @@ -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 @@ -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( @@ -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 @@ -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 @@ -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, @@ -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 @@ -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, diff --git a/tests/pams/test_market.py b/tests/pams/test_market.py index 83e35ba3..839d4b59 100644 --- a/tests/pams/test_market.py +++ b/tests/pams/test_market.py @@ -13,6 +13,7 @@ from pams import Cancel from pams import Market from pams import Order +from pams.logs.base import ExpirationLog from pams.logs.base import Logger from pams.simulator import Simulator @@ -749,3 +750,54 @@ def test_execution_order_pattern10(self) -> None: assert market.remain_executable_orders() logs = market._execution() assert len(logs) == 2 + + def test_expiration_orrder_pattern01(self) -> None: + logger = Logger() + market = self.base_class( + market_id=0, + prng=random.Random(42), + logger=logger, + simulator=Simulator(prng=random.Random(42)), + name="test", + ) + market._update_time(1.0) + market._is_running = True + order = Order( + agent_id=0, market_id=0, is_buy=False, kind=MARKET_ORDER, volume=2, ttl=1 + ) + market._add_order(order) + order = Order( + agent_id=0, market_id=0, is_buy=True, kind=MARKET_ORDER, volume=1, ttl=1 + ) + market._add_order(order) + market._update_time(1.0) + market._update_time(1.0) + assert ( + len([log for log in logger.pending_logs if isinstance(log, ExpirationLog)]) + == 2 + ) + + def test_expiration_orrder_pattern02(self) -> None: + logger = Logger() + market = self.base_class( + market_id=0, + prng=random.Random(42), + logger=logger, + simulator=Simulator(prng=random.Random(42)), + name="test", + ) + market._update_time(1.0) + market._is_running = True + order = Order( + agent_id=0, market_id=0, is_buy=False, kind=MARKET_ORDER, volume=2, ttl=1 + ) + market._add_order(order) + order = Order( + agent_id=0, market_id=0, is_buy=True, kind=MARKET_ORDER, volume=1, ttl=1 + ) + market._add_order(order) + market._set_time(time=2, next_fundamental_price=1.0) + assert ( + len([log for log in logger.pending_logs if isinstance(log, ExpirationLog)]) + == 2 + )